harbor/src/core/api/projectmember.go

306 lines
9.0 KiB
Go

// Copyright 2018 Project Harbor Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package api
import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/dao/group"
"github.com/goharbor/harbor/src/common/dao/project"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/auth"
"github.com/goharbor/harbor/src/core/config"
)
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
type ProjectMemberAPI struct {
BaseController
id int
entityID int
entityType string
project *models.Project
groupType int
}
// ErrDuplicateProjectMember ...
var ErrDuplicateProjectMember = errors.New("The project member specified already exist")
// ErrInvalidRole ...
var ErrInvalidRole = errors.New("Failed to update project member, role is not in 1,2,3")
// Prepare validates the URL and parms
func (pma *ProjectMemberAPI) Prepare() {
pma.BaseController.Prepare()
if !pma.SecurityCtx.IsAuthenticated() {
pma.SendUnAuthorizedError(errors.New("Unauthorized"))
return
}
pid, err := pma.GetInt64FromPath(":pid")
if err != nil || pid <= 0 {
text := "invalid project ID: "
if err != nil {
text += err.Error()
} else {
text += fmt.Sprintf("%d", pid)
}
pma.SendBadRequestError(errors.New(text))
return
}
project, err := pma.ProjectMgr.Get(pid)
if err != nil {
pma.ParseAndHandleError(fmt.Sprintf("failed to get project %d", pid), err)
return
}
if project == nil {
pma.SendNotFoundError(fmt.Errorf("project %d not found", pid))
return
}
pma.project = project
pmid, err := pma.GetInt64FromPath(":pmid")
if err != nil {
log.Warningf("Failed to get pmid from path, error %v", err)
}
if pmid <= 0 && (pma.Ctx.Input.IsPut() || pma.Ctx.Input.IsDelete()) {
pma.SendBadRequestError(fmt.Errorf("The project member id is invalid, pmid:%s", pma.GetStringFromPath(":pmid")))
return
}
pma.id = int(pmid)
authMode, err := config.AuthMode()
if err != nil {
pma.SendInternalServerError(fmt.Errorf("failed to get authentication mode"))
}
if authMode == common.LDAPAuth {
pma.groupType = common.LDAPGroupType
} else if authMode == common.HTTPAuth {
pma.groupType = common.HTTPGroupType
}
}
func (pma *ProjectMemberAPI) requireAccess(action rbac.Action) bool {
return pma.RequireProjectAccess(pma.project.ProjectID, action, rbac.ResourceMember)
}
// Get ...
func (pma *ProjectMemberAPI) Get() {
projectID := pma.project.ProjectID
queryMember := models.Member{}
queryMember.ProjectID = projectID
pma.Data["json"] = make([]models.Member, 0)
if pma.id == 0 {
if !pma.requireAccess(rbac.ActionList) {
return
}
entityname := pma.GetString("entityname")
memberList, err := project.SearchMemberByName(projectID, entityname)
if err != nil {
pma.SendInternalServerError(fmt.Errorf("Failed to query database for member list, error: %v", err))
return
}
if len(memberList) > 0 {
pma.Data["json"] = memberList
}
} else {
// return a specific member
queryMember.ID = pma.id
memberList, err := project.GetProjectMember(queryMember)
if err != nil {
pma.SendInternalServerError(fmt.Errorf("Failed to query database for member list, error: %v", err))
return
}
if len(memberList) == 0 {
pma.SendNotFoundError(fmt.Errorf("The project member does not exist, pmid:%v", pma.id))
return
}
if !pma.requireAccess(rbac.ActionRead) {
return
}
pma.Data["json"] = memberList[0]
}
pma.ServeJSON()
}
// Post ... Add a project member
func (pma *ProjectMemberAPI) Post() {
if !pma.requireAccess(rbac.ActionCreate) {
return
}
projectID := pma.project.ProjectID
var request models.MemberReq
if err := pma.DecodeJSONReq(&request); err != nil {
pma.SendBadRequestError(err)
return
}
request.MemberGroup.LdapGroupDN = strings.TrimSpace(request.MemberGroup.LdapGroupDN)
pmid, err := AddProjectMember(projectID, request)
if err == auth.ErrorGroupNotExist || err == auth.ErrorUserNotExist {
pma.SendBadRequestError(fmt.Errorf("Failed to add project member, error: %v", err))
return
} else if err == auth.ErrDuplicateLDAPGroup {
pma.SendConflictError(fmt.Errorf("Failed to add project member, already exist group or project member, groupDN:%v", request.MemberGroup.LdapGroupDN))
return
} else if err == ErrDuplicateProjectMember {
pma.SendConflictError(fmt.Errorf("Failed to add project member, already exist group or project member, groupMemberID:%v", request.MemberGroup.ID))
return
} else if err == ErrInvalidRole {
pma.SendBadRequestError(fmt.Errorf("Invalid role ID, role ID %v", request.Role))
return
} else if err == auth.ErrInvalidLDAPGroupDN {
pma.SendBadRequestError(fmt.Errorf("Invalid LDAP DN: %v", request.MemberGroup.LdapGroupDN))
return
} else if err != nil {
pma.SendInternalServerError(fmt.Errorf("Failed to add project member, error: %v", err))
return
}
pma.Redirect(http.StatusCreated, strconv.FormatInt(int64(pmid), 10))
}
// Put ... Update an exist project member
func (pma *ProjectMemberAPI) Put() {
if !pma.requireAccess(rbac.ActionUpdate) {
return
}
pid := pma.project.ProjectID
pmID := pma.id
var req models.Member
if err := pma.DecodeJSONReq(&req); err != nil {
pma.SendBadRequestError(err)
return
}
if !isValidRole(req.Role) {
pma.SendBadRequestError(fmt.Errorf("Invalid role id %v", req.Role))
return
}
err := project.UpdateProjectMemberRole(pmID, req.Role)
if err != nil {
pma.SendInternalServerError(fmt.Errorf("Failed to update DB to add project user role, project id: %d, pmid : %d, role id: %d", pid, pmID, req.Role))
return
}
}
// Delete ...
func (pma *ProjectMemberAPI) Delete() {
if !pma.requireAccess(rbac.ActionDelete) {
return
}
pmid := pma.id
err := project.DeleteProjectMemberByID(pmid)
if err != nil {
pma.SendInternalServerError(fmt.Errorf("Failed to delete project roles for user, project member id: %d, error: %v", pmid, err))
return
}
}
// AddProjectMember ...
func AddProjectMember(projectID int64, request models.MemberReq) (int, error) {
var member models.Member
member.ProjectID = projectID
member.Role = request.Role
member.EntityType = common.GroupMember
if request.MemberUser.UserID > 0 {
member.EntityID = request.MemberUser.UserID
member.EntityType = common.UserMember
} else if request.MemberGroup.ID > 0 {
member.EntityID = request.MemberGroup.ID
} else if len(request.MemberUser.Username) > 0 {
var userID int
member.EntityType = common.UserMember
u, err := dao.GetUser(models.User{Username: request.MemberUser.Username})
if err != nil {
return 0, err
}
if u != nil {
userID = u.UserID
} else {
userID, err = auth.SearchAndOnBoardUser(request.MemberUser.Username)
if err != nil {
return 0, err
}
}
member.EntityID = userID
} else if len(request.MemberGroup.LdapGroupDN) > 0 {
request.MemberGroup.GroupType = common.LDAPGroupType
// If groupname provided, use the provided groupname to name this group
groupID, err := auth.SearchAndOnBoardGroup(request.MemberGroup.LdapGroupDN, request.MemberGroup.GroupName)
if err != nil {
return 0, err
}
member.EntityID = groupID
} else if len(request.MemberGroup.GroupName) > 0 && request.MemberGroup.GroupType == common.HTTPGroupType || request.MemberGroup.GroupType == common.OIDCGroupType {
ugs, err := group.QueryUserGroup(models.UserGroup{GroupName: request.MemberGroup.GroupName, GroupType: request.MemberGroup.GroupType})
if err != nil {
return 0, err
}
if len(ugs) == 0 {
groupID, err := auth.SearchAndOnBoardGroup(request.MemberGroup.GroupName, "")
if err != nil {
return 0, err
}
member.EntityID = groupID
} else {
member.EntityID = ugs[0].ID
}
}
if member.EntityID <= 0 {
return 0, fmt.Errorf("Can not get valid member entity, request: %+v", request)
}
// Check if member already exist in current project
memberList, err := project.GetProjectMember(models.Member{
ProjectID: member.ProjectID,
EntityID: member.EntityID,
EntityType: member.EntityType,
})
if err != nil {
return 0, err
}
if len(memberList) > 0 {
return 0, ErrDuplicateProjectMember
}
if !isValidRole(member.Role) {
// Return invalid role error
return 0, ErrInvalidRole
}
return project.AddProjectMember(member)
}
func isValidRole(role int) bool {
switch role {
case common.RoleProjectAdmin,
common.RoleMaster,
common.RoleDeveloper,
common.RoleGuest,
common.RoleLimitedGuest:
return true
default:
return false
}
}