mirror of https://github.com/goharbor/harbor.git
306 lines
9.0 KiB
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
|
|
}
|
|
}
|