From 6cd1813b86acfdf8a8ddd0a3a9dcc5b79d638914 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Thu, 19 May 2016 18:36:40 +0800 Subject: [PATCH 1/7] 1. user can get repositories of public project without login 2. add basic auth support for repository API 3. system admin can CRUD members of all projects --- api/member.go | 97 ++++++++++------------------------------------- api/repository.go | 68 ++++++++++++++++++++++++--------- api/utils.go | 48 +++++++++++++++++++---- 3 files changed, 112 insertions(+), 101 deletions(-) diff --git a/api/member.go b/api/member.go index 2ae7c9c53..b86e36f5d 100644 --- a/api/member.go +++ b/api/member.go @@ -114,23 +114,10 @@ func (pma *ProjectMemberAPI) Get() { // Post ... func (pma *ProjectMemberAPI) Post() { - pid := pma.project.ProjectID - - //userQuery := models.User{UserID: pma.currentUserID, RoleID: models.PROJECTADMIN} - rolelist, err := dao.GetUserProjectRoles(pma.currentUserID, pid) - if err != nil { - log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err) - pma.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - hasProjectAdminRole := false - for _, role := range rolelist { - if role.RoleID == models.PROJECTADMIN { - hasProjectAdminRole = true - break - } - } - if !hasProjectAdminRole { - log.Warningf("Current user, id: %d does not have project admin role for project, id:", pma.currentUserID, pid) + currentUserID := pma.currentUserID + projectID := pma.project.ProjectID + if !hasProjectAdminRole(currentUserID, projectID) { + log.Warningf("Current user, id: %d does not have project admin role for project, id:", currentUserID, projectID) pma.RenderError(http.StatusForbidden, "") return } @@ -144,21 +131,21 @@ func (pma *ProjectMemberAPI) Post() { pma.RenderError(http.StatusNotFound, "User does not exist") return } - rolelist, err = dao.GetUserProjectRoles(userID, pid) + rolelist, err := dao.GetUserProjectRoles(userID, projectID) if err != nil { log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err) pma.CustomAbort(http.StatusInternalServerError, "Internal error.") } if len(rolelist) > 0 { - log.Warningf("user is already added to project, user id: %d, project id: %d", userID, pid) + log.Warningf("user is already added to project, user id: %d, project id: %d", userID, projectID) pma.RenderError(http.StatusConflict, "user is ready in project") return } for _, rid := range req.Roles { - err = dao.AddProjectMember(pid, userID, int(rid)) + err = dao.AddProjectMember(projectID, userID, int(rid)) if err != nil { - log.Errorf("Failed to update DB to add project user role, project id: %d, user id: %d, role id: %d", pid, userID, rid) + log.Errorf("Failed to update DB to add project user role, project id: %d, user id: %d, role id: %d", projectID, userID, rid) pma.RenderError(http.StatusInternalServerError, "Failed to update data in database") return } @@ -167,27 +154,16 @@ func (pma *ProjectMemberAPI) Post() { // Put ... func (pma *ProjectMemberAPI) Put() { + currentUserID := pma.currentUserID pid := pma.project.ProjectID - mid := pma.memberID - - rolelist, err := dao.GetUserProjectRoles(pma.currentUserID, pid) - if err != nil { - log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err) - pma.CustomAbort(http.StatusInternalServerError, "Internal error.") - } - hasProjectAdminRole := false - for _, role := range rolelist { - if role.RoleID == models.PROJECTADMIN { - hasProjectAdminRole = true - break - } - } - - if !hasProjectAdminRole { - log.Warningf("Current user, id: %d does not have project admin role for project, id: %d", pma.currentUserID, pid) + if !hasProjectAdminRole(currentUserID, pid) { + log.Warningf("Current user, id: %d does not have project admin role for project, id:", currentUserID, pid) pma.RenderError(http.StatusForbidden, "") return } + + mid := pma.memberID + var req memberReq pma.DecodeJSONReq(&req) roleList, err := dao.GetUserProjectRoles(mid, pid) @@ -217,51 +193,20 @@ func (pma *ProjectMemberAPI) Put() { // Delete ... func (pma *ProjectMemberAPI) Delete() { + currentUserID := pma.currentUserID pid := pma.project.ProjectID - mid := pma.memberID - - rolelist, err := dao.GetUserProjectRoles(pma.currentUserID, pid) - hasProjectAdminRole := false - for _, role := range rolelist { - if role.RoleID == models.PROJECTADMIN { - hasProjectAdminRole = true - break - } - } - - if !hasProjectAdminRole { - log.Warningf("Current user, id: %d does not have project admin role for project, id: %d", pma.currentUserID, pid) + if !hasProjectAdminRole(currentUserID, pid) { + log.Warningf("Current user, id: %d does not have project admin role for project, id:", currentUserID, pid) pma.RenderError(http.StatusForbidden, "") return } - err = dao.DeleteProjectMember(pid, mid) + + mid := pma.memberID + + err := dao.DeleteProjectMember(pid, mid) if err != nil { log.Errorf("Failed to delete project roles for user, user id: %d, project id: %d, error: %v", mid, pid, err) pma.RenderError(http.StatusInternalServerError, "Failed to update data in DB") return } } - -//sysadmin has all privileges to all projects -func listRoles(userID int, projectID int64) ([]models.Role, error) { - roles := make([]models.Role, 1) - isSysAdmin, err := dao.IsAdminRole(userID) - if err != nil { - return roles, err - } - if isSysAdmin { - role, err := dao.GetRoleByID(models.PROJECTADMIN) - if err != nil { - return roles, err - } - roles = append(roles, *role) - return roles, nil - } - - rs, err := dao.GetUserProjectRoles(userID, projectID) - if err != nil { - return roles, err - } - roles = append(roles, rs...) - return roles, nil -} diff --git a/api/repository.go b/api/repository.go index a827d58bc..ddf66c335 100644 --- a/api/repository.go +++ b/api/repository.go @@ -38,19 +38,19 @@ import ( // the security of registry type RepositoryAPI struct { BaseAPI - userID int + //userID int } // Prepare will set a non existent user ID in case the request tries to view repositories under a project he doesn't has permission. -func (ra *RepositoryAPI) Prepare() { - ra.userID = ra.ValidateUser() -} +//func (ra *RepositoryAPI) Prepare() { +// ra.userID = ra.ValidateUser() +//} // Get ... func (ra *RepositoryAPI) Get() { - projectID, err0 := ra.GetInt64("project_id") - if err0 != nil { - log.Errorf("Failed to get project id, error: %v", err0) + projectID, err := ra.GetInt64("project_id") + if err != nil { + log.Errorf("Failed to get project id, error: %v", err) ra.RenderError(http.StatusBadRequest, "Invalid project id") return } @@ -64,9 +64,14 @@ func (ra *RepositoryAPI) Get() { ra.RenderError(http.StatusNotFound, "") return } - if p.Public == 0 && !checkProjectPermission(ra.userID, projectID) { - ra.RenderError(http.StatusForbidden, "") - return + + if p.Public == 0 { + userID := ra.ValidateUser() + + if !checkProjectPermission(userID, projectID) { + ra.RenderError(http.StatusForbidden, "") + return + } } repoList, err := svc_utils.GetRepoFromCache() @@ -239,15 +244,44 @@ func (ra *RepositoryAPI) GetManifests() { } func (ra *RepositoryAPI) initializeRepositoryClient(repoName string) (r *registry.Repository, err error) { - u := models.User{ - UserID: ra.userID, - } - user, err := dao.GetUser(u) - if err != nil { - return nil, err + var username string + var sessionUsername, sessionUserID interface{} + + // get username from basic auth + username, _, ok := ra.Ctx.Request.BasicAuth() + if ok { + goto enter } + // get username from session + sessionUsername = ra.GetSession("username") + if sessionUsername != nil { + username, ok = sessionUsername.(string) + if ok { + goto enter + } + } + + // if username does not exist in session, try to get userId from sessiion + // and then get username from DB according to the userId + sessionUserID = ra.GetSession("userId") + if sessionUserID != nil { + userID, ok := sessionUserID.(int) + if ok { + u := models.User{ + UserID: userID, + } + user, err := dao.GetUser(u) + if err != nil { + return nil, err + } + username = user.Username + goto enter + } + } + +enter: endpoint := os.Getenv("REGISTRY_URL") - return registry.NewRepositoryWithUsername(repoName, endpoint, user.Username) + return registry.NewRepositoryWithUsername(repoName, endpoint, username) } diff --git a/api/utils.go b/api/utils.go index 826484ac3..97799cc55 100644 --- a/api/utils.go +++ b/api/utils.go @@ -22,20 +22,52 @@ import ( ) func checkProjectPermission(userID int, projectID int64) bool { - exist, err := dao.IsAdminRole(userID) + roles, err := listRoles(userID, projectID) if err != nil { - log.Errorf("Error occurred in IsAdminRole, error: %v", err) + log.Errorf("error occurred in getProjectPermission: %v", err) return false } - if exist { - return true - } - roleList, err := dao.GetUserProjectRoles(userID, projectID) + return len(roles) > 0 +} + +func hasProjectAdminRole(userID int, projectID int64) bool { + roles, err := listRoles(userID, projectID) if err != nil { - log.Errorf("Error occurred in GetUserProjectRoles, error: %v", err) + log.Errorf("error occurred in getProjectPermission: %v", err) return false } - return len(roleList) > 0 + + for _, role := range roles { + if role.RoleID == models.PROJECTADMIN { + return true + } + } + + return false +} + +//sysadmin has all privileges to all projects +func listRoles(userID int, projectID int64) ([]models.Role, error) { + roles := make([]models.Role, 1) + isSysAdmin, err := dao.IsAdminRole(userID) + if err != nil { + return roles, err + } + if isSysAdmin { + role, err := dao.GetRoleByID(models.PROJECTADMIN) + if err != nil { + return roles, err + } + roles = append(roles, *role) + return roles, nil + } + + rs, err := dao.GetUserProjectRoles(userID, projectID) + if err != nil { + return roles, err + } + roles = append(roles, rs...) + return roles, nil } func checkUserExists(name string) int { From 8c86e786c233a7c1239c36f7645ebc6f4d2ce6d8 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 20 May 2016 11:16:19 +0800 Subject: [PATCH 2/7] user can get public projects without login --- api/project.go | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/api/project.go b/api/project.go index 90ccbdb8c..cd65dce5f 100644 --- a/api/project.go +++ b/api/project.go @@ -43,7 +43,6 @@ const projectNameMaxLen int = 30 // Prepare validates the URL and the user func (p *ProjectAPI) Prepare() { - p.userID = p.ValidateUser() idStr := p.Ctx.Input.Param(":id") if len(idStr) > 0 { var err error @@ -65,6 +64,8 @@ func (p *ProjectAPI) Prepare() { // Post ... func (p *ProjectAPI) Post() { + p.userID = p.ValidateUser() + var req projectReq var public int p.DecodeJSONReq(&req) @@ -99,15 +100,24 @@ func (p *ProjectAPI) Post() { // Head ... func (p *ProjectAPI) Head() { projectName := p.GetString("project_name") - result, err := dao.ProjectExists(projectName) + project, err := dao.GetProjectByName(projectName) if err != nil { - log.Errorf("Error while communicating with DB, error: %v", err) - p.RenderError(http.StatusInternalServerError, "Error while communicating with DB") + 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 { return } - if !result { - p.RenderError(http.StatusNotFound, "") - return + + userID := p.ValidateUser() + if project == nil { + p.CustomAbort(http.StatusNotFound, http.StatusText(http.StatusNotFound)) + } + + if !checkProjectPermission(userID, project.ProjectID) { + p.CustomAbort(http.StatusForbidden, http.StatusText(http.StatusForbidden)) } } @@ -132,6 +142,8 @@ func (p *ProjectAPI) Get() { if public == 1 { projectList, err = dao.GetPublicProjects(projectName) } else { + //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) @@ -164,6 +176,8 @@ func (p *ProjectAPI) Get() { // Put ... func (p *ProjectAPI) Put() { + p.userID = p.ValidateUser() + var req projectReq var public int @@ -192,6 +206,7 @@ func (p *ProjectAPI) Put() { // FilterAccessLog handles GET to /api/projects/{}/logs func (p *ProjectAPI) FilterAccessLog() { + p.userID = p.ValidateUser() var filter models.AccessLog p.DecodeJSONReq(&filter) From 792080373549e9857562d802ec554e76a3b4499c Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 20 May 2016 11:32:12 +0800 Subject: [PATCH 3/7] handle project name is nil --- api/project.go | 4 ++++ dao/project.go | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/api/project.go b/api/project.go index cd65dce5f..0a80db759 100644 --- a/api/project.go +++ b/api/project.go @@ -100,6 +100,10 @@ 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) diff --git a/dao/project.go b/dao/project.go index 730b1f331..399cad6aa 100644 --- a/dao/project.go +++ b/dao/project.go @@ -250,7 +250,6 @@ func getProjects(public int, projectName string) ([]models.Project, error) { } sql += " order by name " var projects []models.Project - log.Debugf("sql xxx", sql) if _, err := o.Raw(sql, queryParam).QueryRows(&projects); err != nil { return nil, err } From 1919f19459d5e0a588d45fedbc472db302599be3 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 20 May 2016 11:38:24 +0800 Subject: [PATCH 4/7] remove useless codes --- api/repository.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/api/repository.go b/api/repository.go index ddf66c335..b860687fb 100644 --- a/api/repository.go +++ b/api/repository.go @@ -38,14 +38,8 @@ import ( // the security of registry type RepositoryAPI struct { BaseAPI - //userID int } -// Prepare will set a non existent user ID in case the request tries to view repositories under a project he doesn't has permission. -//func (ra *RepositoryAPI) Prepare() { -// ra.userID = ra.ValidateUser() -//} - // Get ... func (ra *RepositoryAPI) Get() { projectID, err := ra.GetInt64("project_id") From 0b30d04455f47af8f4ce41cce0ac984792096c02 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 20 May 2016 14:02:44 +0800 Subject: [PATCH 5/7] sort tags by name --- api/repository.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/api/repository.go b/api/repository.go index b860687fb..961da1a88 100644 --- a/api/repository.go +++ b/api/repository.go @@ -19,6 +19,7 @@ import ( "encoding/json" "net/http" "os" + "sort" "strconv" "strings" "time" @@ -184,6 +185,8 @@ func (ra *RepositoryAPI) GetTags() { tags = append(tags, ts...) + sort.Strings(tags) + ra.Data["json"] = tags ra.ServeJSON() } From 2a0571fc64a8faf6f788d646f254236a3a81402b Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Thu, 26 May 2016 13:26:10 +0800 Subject: [PATCH 6/7] remove goto --- api/repository.go | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/api/repository.go b/api/repository.go index 961da1a88..ed40644a0 100644 --- a/api/repository.go +++ b/api/repository.go @@ -105,7 +105,7 @@ func (ra *RepositoryAPI) Delete() { ra.CustomAbort(http.StatusBadRequest, "repo_name is nil") } - rc, err := ra.initializeRepositoryClient(repoName) + rc, err := ra.initRepositoryClient(repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") @@ -164,7 +164,7 @@ func (ra *RepositoryAPI) GetTags() { ra.CustomAbort(http.StatusBadRequest, "repo_name is nil") } - rc, err := ra.initializeRepositoryClient(repoName) + rc, err := ra.initRepositoryClient(repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") @@ -200,7 +200,7 @@ func (ra *RepositoryAPI) GetManifests() { ra.CustomAbort(http.StatusBadRequest, "repo_name or tag is nil") } - rc, err := ra.initializeRepositoryClient(repoName) + rc, err := ra.initRepositoryClient(repoName) if err != nil { log.Errorf("error occurred while initializing repository client for %s: %v", repoName, err) ra.CustomAbort(http.StatusInternalServerError, "internal error") @@ -240,28 +240,36 @@ func (ra *RepositoryAPI) GetManifests() { ra.ServeJSON() } -func (ra *RepositoryAPI) initializeRepositoryClient(repoName string) (r *registry.Repository, err error) { - var username string - var sessionUsername, sessionUserID interface{} +func (ra *RepositoryAPI) initRepositoryClient(repoName string) (r *registry.Repository, err error) { + username, err := ra.getUsername() + if err != nil { + return nil, err + } + endpoint := os.Getenv("REGISTRY_URL") + + return registry.NewRepositoryWithUsername(repoName, endpoint, username) +} + +func (ra *RepositoryAPI) getUsername() (string, error) { // get username from basic auth username, _, ok := ra.Ctx.Request.BasicAuth() if ok { - goto enter + return username, nil } // get username from session - sessionUsername = ra.GetSession("username") + sessionUsername := ra.GetSession("username") if sessionUsername != nil { username, ok = sessionUsername.(string) if ok { - goto enter + return username, nil } } // if username does not exist in session, try to get userId from sessiion // and then get username from DB according to the userId - sessionUserID = ra.GetSession("userId") + sessionUserID := ra.GetSession("userId") if sessionUserID != nil { userID, ok := sessionUserID.(int) if ok { @@ -270,15 +278,12 @@ func (ra *RepositoryAPI) initializeRepositoryClient(repoName string) (r *registr } user, err := dao.GetUser(u) if err != nil { - return nil, err + return "", err } - username = user.Username - goto enter + + return user.Username, nil } } -enter: - endpoint := os.Getenv("REGISTRY_URL") - - return registry.NewRepositoryWithUsername(repoName, endpoint, username) + return "", nil } From 6b2b6dedd808d56619491634ceb9bc26e887cff1 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Thu, 26 May 2016 13:59:03 +0800 Subject: [PATCH 7/7] support Get /api/projects/:id --- api/project.go | 19 +++++++++++++++++++ ui/router.go | 1 + 2 files changed, 20 insertions(+) diff --git a/api/project.go b/api/project.go index 0a80db759..5d2a3cf1f 100644 --- a/api/project.go +++ b/api/project.go @@ -127,6 +127,25 @@ func (p *ProjectAPI) Head() { // Get ... func (p *ProjectAPI) Get() { + project, err := dao.GetProjectByID(p.projectID) + if err != nil { + log.Errorf("failed to get project %d: %v", p.projectID, err) + p.CustomAbort(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + } + + if project.Public == 0 { + userID := p.ValidateUser() + if !checkProjectPermission(userID, p.projectID) { + p.CustomAbort(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized)) + } + } + + p.Data["json"] = project + p.ServeJSON() +} + +// List ... +func (p *ProjectAPI) List() { var projectList []models.Project projectName := p.GetString("project_name") if len(projectName) > 0 { diff --git a/ui/router.go b/ui/router.go index 1218424c2..49c139288 100644 --- a/ui/router.go +++ b/ui/router.go @@ -53,6 +53,7 @@ func initRouters() { //API: beego.Router("/api/search", &api.SearchAPI{}) beego.Router("/api/projects/:pid/members/?:mid", &api.ProjectMemberAPI{}) + beego.Router("/api/projects/", &api.ProjectAPI{}, "get:List") beego.Router("/api/projects/?:id", &api.ProjectAPI{}) beego.Router("/api/statistics", &api.StatisticAPI{}) beego.Router("/api/projects/:id/logs/filter", &api.ProjectAPI{}, "post:FilterAccessLog")