mirror of
https://github.com/goharbor/harbor.git
synced 2024-11-22 10:15:35 +01:00
Merge pull request #14577 from stonezdj/21mar30_projectmember
Refactor project member api to new programming model
This commit is contained in:
commit
81a72c0435
@ -176,170 +176,6 @@ paths:
|
||||
description: Project or metadata does not exist.
|
||||
'500':
|
||||
description: Internal server errors.
|
||||
'/projects/{project_id}/members':
|
||||
get:
|
||||
summary: Get all project member information
|
||||
description: Get all project member information
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: entityname
|
||||
in: query
|
||||
type: string
|
||||
description: The entity name to search.
|
||||
tags:
|
||||
- Products
|
||||
responses:
|
||||
'200':
|
||||
description: Get project members successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ProjectMemberEntity'
|
||||
'400':
|
||||
description: The project id is invalid.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'404':
|
||||
description: Project ID does not exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
post:
|
||||
summary: Create project member
|
||||
description: 'Create project member relationship, the member can be one of the user_member and group_member, The user_member need to specify user_id or username. If the user already exist in harbor DB, specify the user_id, If does not exist in harbor DB, it will SearchAndOnBoard the user. The group_member need to specify id or ldap_group_dn. If the group already exist in harbor DB. specify the user group''s id, If does not exist, it will SearchAndOnBoard the group. '
|
||||
tags:
|
||||
- Products
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: project_member
|
||||
in: body
|
||||
schema:
|
||||
$ref: '#/definitions/ProjectMember'
|
||||
responses:
|
||||
'201':
|
||||
description: Project member created successfully.
|
||||
headers:
|
||||
Location:
|
||||
type: string
|
||||
description: The URL of the created resource
|
||||
'400':
|
||||
description: 'Illegal format of project member or project id is invalid, or LDAP DN is invalid.'
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'409':
|
||||
description: A user group with same group name already exist or an LDAP user group with same DN already exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/projects/{project_id}/members/{mid}':
|
||||
get:
|
||||
summary: Get the project member information
|
||||
description: Get the project member information
|
||||
tags:
|
||||
- Products
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: mid
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: The member ID
|
||||
responses:
|
||||
'200':
|
||||
description: Project member retrieved successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/ProjectMemberEntity'
|
||||
'400':
|
||||
description: 'Illegal format of project member or invalid project id, member id.'
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'404':
|
||||
description: Project or projet member does not exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
put:
|
||||
summary: Update project member
|
||||
description: Update project member relationship
|
||||
tags:
|
||||
- Products
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: mid
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Member ID.
|
||||
- name: role
|
||||
in: body
|
||||
schema:
|
||||
$ref: '#/definitions/RoleRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Project member updated successfully.
|
||||
'400':
|
||||
description: 'Invalid role id, it should be 1,2 or 3, or invalid project id, or invalid member id.'
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'404':
|
||||
description: project or project member does not exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
delete:
|
||||
summary: Delete project member
|
||||
tags:
|
||||
- Products
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: mid
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Member ID.
|
||||
responses:
|
||||
'200':
|
||||
description: Project member deleted successfully.
|
||||
'400':
|
||||
description: The project id or project member id is invalid.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
/statistics:
|
||||
get:
|
||||
summary: Get projects number and repositories number relevant to the user
|
||||
|
@ -447,6 +447,170 @@ paths:
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/projects/{project_name_or_id}/members':
|
||||
get:
|
||||
summary: Get all project member information
|
||||
description: Get all project member information
|
||||
operationId: listProjectMembers
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- $ref: '#/parameters/page'
|
||||
- $ref: '#/parameters/pageSize'
|
||||
- name: entityname
|
||||
in: query
|
||||
type: string
|
||||
description: The entity name to search.
|
||||
tags:
|
||||
- member
|
||||
responses:
|
||||
'200':
|
||||
description: Get project members successfully.
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of members
|
||||
type: integer
|
||||
Link:
|
||||
description: Link refers to the previous page and next page
|
||||
type: string
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/ProjectMemberEntity'
|
||||
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
post:
|
||||
summary: Create project member
|
||||
operationId: createProjectMember
|
||||
description: 'Create project member relationship, the member can be one of the user_member and group_member, The user_member need to specify user_id or username. If the user already exist in harbor DB, specify the user_id, If does not exist in harbor DB, it will SearchAndOnBoard the user. The group_member need to specify id or ldap_group_dn. If the group already exist in harbor DB. specify the user group''s id, If does not exist, it will SearchAndOnBoard the group. '
|
||||
tags:
|
||||
- member
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- name: project_member
|
||||
in: body
|
||||
schema:
|
||||
$ref: '#/definitions/ProjectMember'
|
||||
responses:
|
||||
'201':
|
||||
description: Project member created successfully.
|
||||
headers:
|
||||
Location:
|
||||
type: string
|
||||
description: The URL of the created resource
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'409':
|
||||
$ref: '#/responses/409'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
'/projects/{project_name_or_id}/members/{mid}':
|
||||
get:
|
||||
summary: Get the project member information
|
||||
description: Get the project member information
|
||||
operationId: getProjectMember
|
||||
tags:
|
||||
- member
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- name: mid
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: The member ID
|
||||
responses:
|
||||
'200':
|
||||
description: Project member retrieved successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/ProjectMemberEntity'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
put:
|
||||
summary: Update project member
|
||||
description: Update project member relationship
|
||||
operationId: updateProjectMember
|
||||
tags:
|
||||
- member
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- name: mid
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Member ID.
|
||||
- name: role
|
||||
in: body
|
||||
schema:
|
||||
$ref: '#/definitions/RoleRequest'
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/responses/200'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'404':
|
||||
$ref: '#/responses/404'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
delete:
|
||||
summary: Delete project member
|
||||
operationId: deleteProjectMember
|
||||
tags:
|
||||
- member
|
||||
parameters:
|
||||
- $ref: '#/parameters/requestId'
|
||||
- $ref: '#/parameters/isResourceName'
|
||||
- $ref: '#/parameters/projectNameOrId'
|
||||
- name: mid
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Member ID.
|
||||
responses:
|
||||
'200':
|
||||
$ref: '#/responses/200'
|
||||
'400':
|
||||
$ref: '#/responses/400'
|
||||
'401':
|
||||
$ref: '#/responses/401'
|
||||
'403':
|
||||
$ref: '#/responses/403'
|
||||
'500':
|
||||
$ref: '#/responses/500'
|
||||
/repositories:
|
||||
get:
|
||||
summary: List all authorized repositories
|
||||
@ -4783,6 +4947,7 @@ parameters:
|
||||
required: true
|
||||
type: integer
|
||||
format: int64
|
||||
|
||||
responses:
|
||||
'200':
|
||||
description: Success
|
||||
@ -7392,6 +7557,55 @@ definitions:
|
||||
editable:
|
||||
type: boolean
|
||||
description: The configure item can be updated or not
|
||||
ProjectMemberEntity:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: the project member id
|
||||
project_id:
|
||||
type: integer
|
||||
description: the project id
|
||||
entity_name:
|
||||
type: string
|
||||
description: the name of the group member.
|
||||
role_name:
|
||||
type: string
|
||||
description: the name of the role
|
||||
role_id:
|
||||
type: integer
|
||||
description: the role id
|
||||
entity_id:
|
||||
type: integer
|
||||
description: 'the id of entity, if the member is a user, it is user_id in user table. if the member is a user group, it is the user group''s ID in user_group table.'
|
||||
entity_type:
|
||||
type: string
|
||||
description: 'the entity''s type, u for user entity, g for group entity.'
|
||||
ProjectMember:
|
||||
type: object
|
||||
properties:
|
||||
role_id:
|
||||
type: integer
|
||||
description: 'The role id 1 for projectAdmin, 2 for developer, 3 for guest, 4 for maintainer'
|
||||
member_user:
|
||||
$ref: '#/definitions/UserEntity'
|
||||
member_group:
|
||||
$ref: '#/definitions/UserGroup'
|
||||
RoleRequest:
|
||||
type: object
|
||||
properties:
|
||||
role_id:
|
||||
type: integer
|
||||
description: 'The role id 1 for projectAdmin, 2 for developer, 3 for guest, 4 for maintainer'
|
||||
UserEntity:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
type: integer
|
||||
description: The ID of the user.
|
||||
username:
|
||||
type: string
|
||||
description: The name of the user.
|
||||
UserProfile:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -1,403 +0,0 @@
|
||||
// Copyright 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 project
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
_ "github.com/goharbor/harbor/src/core/auth/db"
|
||||
_ "github.com/goharbor/harbor/src/core/auth/ldap"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
||||
// databases := []string{"mysql", "sqlite"}
|
||||
databases := []string{"postgresql"}
|
||||
for _, database := range databases {
|
||||
log.Infof("run test cases for database: %s", database)
|
||||
|
||||
result := 1
|
||||
switch database {
|
||||
case "postgresql":
|
||||
dao.PrepareTestForPostgresSQL()
|
||||
default:
|
||||
log.Fatalf("invalid database: %s", database)
|
||||
}
|
||||
|
||||
// Extract to test utils
|
||||
initSqls := []string{
|
||||
"insert into harbor_user (username, email, password, realname) values ('member_test_01', 'member_test_01@example.com', '123456', 'member_test_01')",
|
||||
"insert into project (name, owner_id) values ('member_test_01', 1)",
|
||||
"insert into user_group (group_name, group_type, ldap_group_dn) values ('test_group_01', 1, 'CN=harbor_users,OU=sample,OU=vmware,DC=harbor,DC=com')",
|
||||
"update project set owner_id = (select user_id from harbor_user where username = 'member_test_01') where name = 'member_test_01'",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_01') , (select user_id from harbor_user where username = 'member_test_01'), 'u', 1)",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_01') , (select id from user_group where group_name = 'test_group_01'), 'g', 1)",
|
||||
|
||||
"insert into harbor_user (username, email, password, realname) values ('member_test_02', 'member_test_02@example.com', '123456', 'member_test_02')",
|
||||
"insert into project (name, owner_id) values ('member_test_02', 1)",
|
||||
"insert into user_group (group_name, group_type, ldap_group_dn) values ('test_group_02', 1, 'CN=harbor_users,OU=sample,OU=vmware,DC=harbor,DC=com')",
|
||||
"update project set owner_id = (select user_id from harbor_user where username = 'member_test_02') where name = 'member_test_02'",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_02') , (select user_id from harbor_user where username = 'member_test_02'), 'u', 1)",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_02') , (select id from user_group where group_name = 'test_group_02'), 'g', 1)",
|
||||
}
|
||||
|
||||
clearSqls := []string{
|
||||
"delete from project where name='member_test_01' or name='member_test_02'",
|
||||
"delete from harbor_user where username='member_test_01' or username='member_test_02' or username='pm_sample'",
|
||||
"delete from user_group",
|
||||
"delete from project_member where id > 1",
|
||||
}
|
||||
dao.PrepareTestData(clearSqls, initSqls)
|
||||
config.Init()
|
||||
result = m.Run()
|
||||
|
||||
if result != 0 {
|
||||
os.Exit(result)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestDeleteProjectMemberByID(t *testing.T) {
|
||||
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||
|
||||
if currentProject == nil || err != nil {
|
||||
fmt.Println("Failed to load project!")
|
||||
} else {
|
||||
fmt.Printf("Load project %+v", currentProject)
|
||||
}
|
||||
var addMember = models.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
EntityID: 1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleDeveloper,
|
||||
}
|
||||
|
||||
pmid, err := AddProjectMember(addMember)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to add project member error: %v", err)
|
||||
}
|
||||
|
||||
type args struct {
|
||||
pmid int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{"Delete created", args{pmid}, false},
|
||||
{"Delete non exist", args{-13}, false},
|
||||
{"Delete non exist", args{13}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := DeleteProjectMemberByID(tt.args.pmid); (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeleteProjectMemberByID() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
func TestAddProjectMember(t *testing.T) {
|
||||
|
||||
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when GetProjectByName: %v", err)
|
||||
}
|
||||
member := models.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
EntityID: 1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
}
|
||||
|
||||
log.Debugf("Current project id %v", currentProject.ProjectID)
|
||||
pmid, err := AddProjectMember(member)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddProjectMember: %v", err)
|
||||
}
|
||||
if pmid == 0 {
|
||||
t.Errorf("Error add project member, pmid=0")
|
||||
}
|
||||
|
||||
queryMember := models.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
ID: pmid,
|
||||
}
|
||||
|
||||
memberList, err := GetProjectMember(queryMember)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to query project member, %v, error: %v", queryMember, err)
|
||||
}
|
||||
|
||||
if len(memberList) == 0 {
|
||||
t.Errorf("Failed to query project member, %v", queryMember)
|
||||
}
|
||||
|
||||
_, err = AddProjectMember(models.Member{
|
||||
ProjectID: -1,
|
||||
EntityID: 1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Should failed with negative projectID")
|
||||
}
|
||||
_, err = AddProjectMember(models.Member{
|
||||
ProjectID: 1,
|
||||
EntityID: -1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("Should failed with negative entityID")
|
||||
}
|
||||
}
|
||||
func TestUpdateProjectMemberRole(t *testing.T) {
|
||||
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||
user := models.User{
|
||||
Username: "pm_sample",
|
||||
Email: "pm_sample@example.com",
|
||||
Realname: "pm_sample",
|
||||
Password: "1234567d",
|
||||
}
|
||||
o := dao.GetOrmer()
|
||||
userID, err := o.Insert(&user)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when add user: %v", err)
|
||||
}
|
||||
member := models.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
EntityID: int(userID),
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
}
|
||||
|
||||
pmid, err := AddProjectMember(member)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in UpdateProjectMember: %v", err)
|
||||
}
|
||||
|
||||
UpdateProjectMemberRole(pmid, common.RoleDeveloper)
|
||||
|
||||
queryMember := models.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
EntityID: int(userID),
|
||||
EntityType: common.UserMember,
|
||||
}
|
||||
|
||||
memberList, err := GetProjectMember(queryMember)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in GetProjectMember: %v", err)
|
||||
}
|
||||
if len(memberList) != 1 {
|
||||
t.Errorf("Error occurred in Failed, size: %d, condition:%+v", len(memberList), queryMember)
|
||||
}
|
||||
memberItem := memberList[0]
|
||||
if memberItem.Role != common.RoleDeveloper || memberItem.Entityname != user.Username {
|
||||
t.Errorf("member doesn't match!")
|
||||
}
|
||||
|
||||
memberList2, err := SearchMemberByName(currentProject.ProjectID, "pm_sample")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when SearchMemberByName: %v", err)
|
||||
}
|
||||
if len(memberList2) == 0 {
|
||||
t.Errorf("Failed to search user pm_sample, project_id:%v, entityname:%v",
|
||||
currentProject.ProjectID, "pm_sample")
|
||||
}
|
||||
|
||||
memberList3, err := SearchMemberByName(currentProject.ProjectID, "")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when SearchMemberByName: %v", err)
|
||||
}
|
||||
if len(memberList3) == 0 {
|
||||
t.Errorf("Failed to search user pm_sample, project_id:%v, entityname is empty",
|
||||
currentProject.ProjectID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetProjectMember(t *testing.T) {
|
||||
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when GetProjectByName: %v", err)
|
||||
}
|
||||
var memberList1 = []*models.Member{
|
||||
{
|
||||
ID: 346,
|
||||
Entityname: "admin",
|
||||
Rolename: "projectAdmin",
|
||||
Role: 1,
|
||||
EntityID: 1,
|
||||
EntityType: "u"},
|
||||
}
|
||||
var memberList2 = []*models.Member{
|
||||
{
|
||||
ID: 398,
|
||||
Entityname: "test_group_01",
|
||||
Rolename: "projectAdmin",
|
||||
Role: 1,
|
||||
EntityType: "g"},
|
||||
}
|
||||
type args struct {
|
||||
queryMember models.Member
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []*models.Member
|
||||
wantErr bool
|
||||
}{
|
||||
{"Query default project member", args{models.Member{ProjectID: currentProject.ProjectID, Entityname: "admin"}}, memberList1, false},
|
||||
{"Query default project member group", args{models.Member{ProjectID: currentProject.ProjectID, Entityname: "test_group_01"}}, memberList2, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetProjectMember(tt.args.queryMember)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetProjectMember() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if len(got) != 1 {
|
||||
t.Errorf("Error occurred when query project member")
|
||||
}
|
||||
itemGot := got[0]
|
||||
itemWant := tt.want[0]
|
||||
|
||||
if itemGot.Entityname != itemWant.Entityname || itemGot.Role != itemWant.Role || itemGot.EntityType != itemWant.EntityType {
|
||||
t.Errorf("test failed, got:%+v, want:%+v", itemGot, itemWant)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetTotalOfProjectMembers(t *testing.T) {
|
||||
currentProject, _ := dao.GetProjectByName("member_test_02")
|
||||
|
||||
type args struct {
|
||||
projectID int64
|
||||
roles []int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want int64
|
||||
wantErr bool
|
||||
}{
|
||||
{"Get total of project admin", args{currentProject.ProjectID, []int{common.RoleProjectAdmin}}, 2, false},
|
||||
{"Get total of maintainer", args{currentProject.ProjectID, []int{common.RoleMaintainer}}, 0, false},
|
||||
{"Get total of developer", args{currentProject.ProjectID, []int{common.RoleDeveloper}}, 0, false},
|
||||
{"Get total of guest", args{currentProject.ProjectID, []int{common.RoleGuest}}, 0, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetTotalOfProjectMembers(tt.args.projectID, tt.args.roles...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetTotalOfProjectMembers() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("GetTotalOfProjectMembers() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRoles(t *testing.T) {
|
||||
// nil user
|
||||
roles, err := ListRoles(nil, 1)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, roles, 0)
|
||||
|
||||
user, err := dao.GetUser(models.User{Username: "member_test_01"})
|
||||
require.Nil(t, err)
|
||||
project, err := dao.GetProjectByName("member_test_01")
|
||||
require.Nil(t, err)
|
||||
|
||||
// user with empty groups
|
||||
roles, err = ListRoles(user, project.ProjectID)
|
||||
require.Nil(t, err)
|
||||
assert.Len(t, roles, 1)
|
||||
|
||||
// user with a group whose ID doesn't exist
|
||||
user.GroupIDs = []int{9999}
|
||||
roles, err = ListRoles(user, project.ProjectID)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, roles, 1)
|
||||
assert.Equal(t, common.RoleProjectAdmin, roles[0])
|
||||
ctx := orm.Context()
|
||||
// user with a valid group
|
||||
groupID, err := usergroup.Mgr.Create(ctx, model.UserGroup{
|
||||
GroupName: "group_for_list_role",
|
||||
GroupType: 1,
|
||||
LdapGroupDN: "CN=list_role_users,OU=sample,OU=vmware,DC=harbor,DC=com",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer usergroup.Mgr.Delete(orm.Context(), groupID)
|
||||
|
||||
memberID, err := AddProjectMember(models.Member{
|
||||
ProjectID: project.ProjectID,
|
||||
Role: common.RoleDeveloper,
|
||||
EntityID: groupID,
|
||||
EntityType: "g",
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer DeleteProjectMemberByID(memberID)
|
||||
|
||||
user.GroupIDs = []int{groupID}
|
||||
roles, err = ListRoles(user, project.ProjectID)
|
||||
require.Nil(t, err)
|
||||
require.Len(t, roles, 2)
|
||||
assert.Equal(t, common.RoleProjectAdmin, roles[0])
|
||||
assert.Equal(t, common.RoleDeveloper, roles[1])
|
||||
}
|
||||
|
||||
func PrepareGroupTest() {
|
||||
initSqls := []string{
|
||||
`insert into user_group (group_name, group_type, ldap_group_dn) values ('harbor_group_01', 1, 'cn=harbor_user,dc=example,dc=com')`,
|
||||
`insert into harbor_user (username, email, password, realname) values ('sample01', 'sample01@example.com', 'harbor12345', 'sample01')`,
|
||||
`insert into project (name, owner_id) values ('group_project', 1)`,
|
||||
`insert into project (name, owner_id) values ('group_project_private', 1)`,
|
||||
`insert into project_metadata (project_id, name, value) values ((select project_id from project where name = 'group_project'), 'public', 'false')`,
|
||||
`insert into project_metadata (project_id, name, value) values ((select project_id from project where name = 'group_project_private'), 'public', 'false')`,
|
||||
`insert into project_member (project_id, entity_id, entity_type, role) values ((select project_id from project where name = 'group_project'), (select id from user_group where group_name = 'harbor_group_01'),'g', 2)`,
|
||||
}
|
||||
|
||||
clearSqls := []string{
|
||||
`delete from project_metadata where project_id in (select project_id from project where name in ('group_project', 'group_project_private'))`,
|
||||
`delete from project where name in ('group_project', 'group_project_private')`,
|
||||
`delete from project_member where project_id in (select project_id from project where name in ('group_project', 'group_project_private'))`,
|
||||
`delete from user_group where group_name = 'harbor_group_01'`,
|
||||
`delete from harbor_user where username = 'sample01'`,
|
||||
}
|
||||
dao.PrepareTestData(clearSqls, initSqls)
|
||||
}
|
234
src/controller/member/controller.go
Normal file
234
src/controller/member/controller.go
Normal file
@ -0,0 +1,234 @@
|
||||
// Copyright 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 member
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/core/auth"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/member"
|
||||
"github.com/goharbor/harbor/src/pkg/member/models"
|
||||
"github.com/goharbor/harbor/src/pkg/project"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
ugModel "github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
)
|
||||
|
||||
// Controller defines the operation related to project member
|
||||
type Controller interface {
|
||||
// Get get the project member with ID
|
||||
Get(ctx context.Context, projectNameOrID interface{}, memberID int) (*models.Member, error)
|
||||
// Create add project member to project
|
||||
Create(ctx context.Context, projectNameOrID interface{}, req Request) (int, error)
|
||||
// Delete member from project
|
||||
Delete(ctx context.Context, projectNameOrID interface{}, memberID int) error
|
||||
// List list all project members with condition
|
||||
List(ctx context.Context, projectNameOrID interface{}, entityName string, query *q.Query) ([]*models.Member, error)
|
||||
// UpdateRole update the project member role
|
||||
UpdateRole(ctx context.Context, projectNameOrID interface{}, memberID int, role int) error
|
||||
// Count get the total amount of project members
|
||||
Count(ctx context.Context, projectNameOrID interface{}, query *q.Query) (int, error)
|
||||
}
|
||||
|
||||
// Request - Project Member Request
|
||||
type Request struct {
|
||||
ProjectID int64 `json:"project_id"`
|
||||
Role int `json:"role_id,omitempty"`
|
||||
MemberUser User `json:"member_user,omitempty"`
|
||||
MemberGroup UserGroup `json:"member_group,omitempty"`
|
||||
}
|
||||
|
||||
// User ...
|
||||
type User struct {
|
||||
UserID int `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
}
|
||||
|
||||
// UserGroup ...
|
||||
type UserGroup struct {
|
||||
ID int `json:"id,omitempty"`
|
||||
GroupName string `json:"group_name,omitempty"`
|
||||
GroupType int `json:"group_type,omitempty"`
|
||||
LdapGroupDN string `json:"ldap_group_dn,omitempty"`
|
||||
}
|
||||
|
||||
// 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")
|
||||
|
||||
type controller struct {
|
||||
userManager user.Manager
|
||||
mgr member.Manager
|
||||
projectMgr project.Manager
|
||||
}
|
||||
|
||||
// NewController ...
|
||||
func NewController() Controller {
|
||||
return &controller{mgr: member.Mgr, projectMgr: project.Mgr, userManager: user.New()}
|
||||
}
|
||||
|
||||
func (c *controller) Count(ctx context.Context, projectNameOrID interface{}, query *q.Query) (int, error) {
|
||||
p, err := c.projectMgr.Get(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return c.mgr.GetTotalOfProjectMembers(ctx, p.ProjectID, query)
|
||||
}
|
||||
|
||||
func (c *controller) UpdateRole(ctx context.Context, projectNameOrID interface{}, memberID int, role int) error {
|
||||
p, err := c.projectMgr.Get(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if p == nil {
|
||||
return errors.BadRequestError(nil).WithMessage("project is not found")
|
||||
}
|
||||
return c.mgr.UpdateRole(ctx, p.ProjectID, memberID, role)
|
||||
}
|
||||
|
||||
func (c *controller) Get(ctx context.Context, projectNameOrID interface{}, memberID int) (*models.Member, error) {
|
||||
p, err := c.projectMgr.Get(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p == nil {
|
||||
return nil, errors.BadRequestError(nil).WithMessage("project is not found")
|
||||
}
|
||||
return c.mgr.Get(ctx, p.ProjectID, memberID)
|
||||
}
|
||||
|
||||
func (c *controller) Create(ctx context.Context, projectNameOrID interface{}, req Request) (int, error) {
|
||||
p, err := c.projectMgr.Get(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if p == nil {
|
||||
return 0, errors.BadRequestError(nil).WithMessage("project is not found")
|
||||
}
|
||||
var member models.Member
|
||||
member.ProjectID = p.ProjectID
|
||||
member.Role = req.Role
|
||||
member.EntityType = common.GroupMember
|
||||
|
||||
if req.MemberUser.UserID > 0 {
|
||||
member.EntityID = req.MemberUser.UserID
|
||||
member.EntityType = common.UserMember
|
||||
} else if req.MemberGroup.ID > 0 {
|
||||
member.EntityID = req.MemberGroup.ID
|
||||
} else if len(req.MemberUser.Username) > 0 {
|
||||
// If username is provided, search userid by username
|
||||
var userID int
|
||||
member.EntityType = common.UserMember
|
||||
u, err := c.userManager.GetByName(ctx, req.MemberUser.Username)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if u != nil {
|
||||
userID = u.UserID
|
||||
} else {
|
||||
userID, err = auth.SearchAndOnBoardUser(req.MemberUser.Username)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
member.EntityID = userID
|
||||
} else if len(req.MemberGroup.LdapGroupDN) > 0 {
|
||||
req.MemberGroup.GroupType = common.LDAPGroupType
|
||||
// If groupname provided, use the provided groupname to name this group
|
||||
groupID, err := auth.SearchAndOnBoardGroup(req.MemberGroup.LdapGroupDN, req.MemberGroup.GroupName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
member.EntityID = groupID
|
||||
} else if len(req.MemberGroup.GroupName) > 0 && req.MemberGroup.GroupType == common.HTTPGroupType || req.MemberGroup.GroupType == common.OIDCGroupType {
|
||||
ugs, err := usergroup.Mgr.List(ctx, ugModel.UserGroup{GroupName: req.MemberGroup.GroupName, GroupType: req.MemberGroup.GroupType})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(ugs) == 0 {
|
||||
groupID, err := auth.SearchAndOnBoardGroup(req.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", req)
|
||||
}
|
||||
|
||||
// Check if member already exist in current project
|
||||
memberList, err := c.mgr.List(ctx, models.Member{
|
||||
ProjectID: member.ProjectID,
|
||||
EntityID: member.EntityID,
|
||||
EntityType: member.EntityType,
|
||||
}, nil)
|
||||
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 c.mgr.AddProjectMember(ctx, member)
|
||||
}
|
||||
|
||||
func isValidRole(role int) bool {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
common.RoleMaintainer,
|
||||
common.RoleDeveloper,
|
||||
common.RoleGuest,
|
||||
common.RoleLimitedGuest:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (c *controller) List(ctx context.Context, projectNameOrID interface{}, entityName string, query *q.Query) ([]*models.Member, error) {
|
||||
p, err := c.projectMgr.Get(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p == nil {
|
||||
return nil, errors.BadRequestError(nil).WithMessage("project is not found")
|
||||
}
|
||||
pm := models.Member{
|
||||
ProjectID: p.ProjectID,
|
||||
Entityname: entityName,
|
||||
}
|
||||
return c.mgr.List(ctx, pm, query)
|
||||
}
|
||||
|
||||
func (c *controller) Delete(ctx context.Context, projectNameOrID interface{}, memberID int) error {
|
||||
p, err := c.projectMgr.Get(ctx, projectNameOrID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.mgr.Delete(ctx, p.ProjectID, memberID)
|
||||
}
|
15
src/controller/member/controller_test.go
Normal file
15
src/controller/member/controller_test.go
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 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 member
|
@ -16,6 +16,7 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -30,9 +31,10 @@ import (
|
||||
|
||||
"github.com/dghubble/sling"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/dao/project"
|
||||
common_http "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/pkg/member"
|
||||
memberModels "github.com/goharbor/harbor/src/pkg/member/models"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -249,8 +251,8 @@ func prepare() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if projAdminPMID, err = project.AddProjectMember(models.Member{
|
||||
ctx := orm.Context()
|
||||
if projAdminPMID, err = member.Mgr.AddProjectMember(ctx, memberModels.Member{
|
||||
ProjectID: 1,
|
||||
Role: common.RoleProjectAdmin,
|
||||
EntityID: int(projAdminID),
|
||||
@ -268,8 +270,7 @@ func prepare() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if projAdminRobotPMID, err = project.AddProjectMember(models.Member{
|
||||
if projAdminRobotPMID, err = member.Mgr.AddProjectMember(ctx, memberModels.Member{
|
||||
ProjectID: 1,
|
||||
Role: common.RoleProjectAdmin,
|
||||
EntityID: int(projAdminRobotID),
|
||||
@ -288,7 +289,7 @@ func prepare() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if projDeveloperPMID, err = project.AddProjectMember(models.Member{
|
||||
if projDeveloperPMID, err = member.Mgr.AddProjectMember(ctx, memberModels.Member{
|
||||
ProjectID: 1,
|
||||
Role: common.RoleDeveloper,
|
||||
EntityID: int(projDeveloperID),
|
||||
@ -307,7 +308,7 @@ func prepare() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if projGuestPMID, err = project.AddProjectMember(models.Member{
|
||||
if projGuestPMID, err = member.Mgr.AddProjectMember(ctx, memberModels.Member{
|
||||
ProjectID: 1,
|
||||
Role: common.RoleGuest,
|
||||
EntityID: int(projGuestID),
|
||||
@ -325,7 +326,7 @@ func prepare() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if projLimitedGuestPMID, err = project.AddProjectMember(models.Member{
|
||||
if projLimitedGuestPMID, err = member.Mgr.AddProjectMember(ctx, memberModels.Member{
|
||||
ProjectID: 1,
|
||||
Role: common.RoleLimitedGuest,
|
||||
EntityID: int(projLimitedGuestID),
|
||||
@ -340,7 +341,7 @@ func clean() {
|
||||
pmids := []int{projAdminPMID, projDeveloperPMID, projGuestPMID}
|
||||
|
||||
for _, id := range pmids {
|
||||
if err := project.DeleteProjectMemberByID(id); err != nil {
|
||||
if err := member.Mgr.Delete(orm.Context(), 1, id); err != nil {
|
||||
fmt.Printf("failed to clean up member %d from project library: %v", id, err)
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,6 @@ func init() {
|
||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/?:name", &MetadataAPI{}, "get:Get")
|
||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/", &MetadataAPI{}, "post:Post")
|
||||
beego.Router("/api/projects/:id([0-9]+)/metadatas/:name", &MetadataAPI{}, "put:Put;delete:Delete")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/members/?:pmid([0-9]+)", &ProjectMemberAPI{})
|
||||
beego.Router("/api/statistics", &StatisticAPI{})
|
||||
beego.Router("/api/email/ping", &EmailAPI{}, "post:Ping")
|
||||
beego.Router("/api/labels", &LabelAPI{}, "post:Post;get:List")
|
||||
|
@ -1,307 +0,0 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"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/core/auth"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
)
|
||||
|
||||
// ProjectMemberAPI handles request to /api/projects/{}/members/{}
|
||||
type ProjectMemberAPI struct {
|
||||
BaseController
|
||||
id int
|
||||
project *models.Project
|
||||
member *models.Member
|
||||
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
|
||||
}
|
||||
pro, err := pma.ProjectCtl.Get(pma.Context(), pid)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
pma.SendNotFoundError(fmt.Errorf("project %d not found", pid))
|
||||
} else {
|
||||
pma.ParseAndHandleError(fmt.Sprintf("failed to get project %d", pid), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
pma.project = pro
|
||||
|
||||
if pma.ParamExistsInPath(":pmid") {
|
||||
pmid, err := pma.GetInt64FromPath(":pmid")
|
||||
if err != nil || pmid <= 0 {
|
||||
pma.SendBadRequestError(fmt.Errorf("The project member id is invalid, pmid:%s", pma.GetStringFromPath(":pmid")))
|
||||
return
|
||||
}
|
||||
pma.id = int(pmid)
|
||||
|
||||
members, err := project.GetProjectMember(models.Member{ProjectID: pid, ID: pma.id})
|
||||
if err != nil {
|
||||
pma.SendInternalServerError(err)
|
||||
return
|
||||
}
|
||||
if len(members) == 0 {
|
||||
pma.SendNotFoundError(fmt.Errorf("project member %d not found in project %d", pmid, pid))
|
||||
return
|
||||
}
|
||||
|
||||
pma.member = members[0]
|
||||
}
|
||||
|
||||
authMode, err := config.AuthMode(orm.Context())
|
||||
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 {
|
||||
if !pma.requireAccess(rbac.ActionRead) {
|
||||
return
|
||||
}
|
||||
pma.Data["json"] = pma.member
|
||||
}
|
||||
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 := usergroup.Mgr.List(orm.Context(), model.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.RoleMaintainer,
|
||||
common.RoleDeveloper,
|
||||
common.RoleGuest,
|
||||
common.RoleLimitedGuest:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
@ -1,414 +0,0 @@
|
||||
// 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 (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/dao/project"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
)
|
||||
|
||||
func TestProjectMemberAPI_Get(t *testing.T) {
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/members",
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/members",
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/0/members",
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("/api/projects/1/members/%d", projAdminPMID),
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/members/121",
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/99999/members/121",
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestProjectMemberAPI_Post(t *testing.T) {
|
||||
userID, err := dao.Register(models.User{
|
||||
Username: "restuser",
|
||||
Password: "Harbor12345",
|
||||
Email: "restuser@example.com",
|
||||
})
|
||||
defer dao.DeleteUser(int(userID))
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when create user: %v", err)
|
||||
}
|
||||
|
||||
ugList, err := usergroup.Mgr.List(orm.Context(), model.UserGroup{GroupType: 1, LdapGroupDN: "cn=harbor_users,ou=sample,ou=vmware,dc=harbor,dc=com"})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to query the user group")
|
||||
}
|
||||
if len(ugList) <= 0 {
|
||||
t.Errorf("Failed to query the user group")
|
||||
}
|
||||
httpUgList, err := usergroup.Mgr.List(orm.Context(), model.UserGroup{GroupType: 2, GroupName: "vsphere.local\\administrators"})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to query the user group")
|
||||
}
|
||||
if len(httpUgList) <= 0 {
|
||||
t.Errorf("Failed to query the user group")
|
||||
}
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberUser: models.User{
|
||||
UserID: int(userID),
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberUser: models.User{
|
||||
UserID: int(userID),
|
||||
},
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberUser: models.User{
|
||||
Username: "notexistuser",
|
||||
},
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberUser: models.User{
|
||||
UserID: 0,
|
||||
},
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/members?entityname=restuser",
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/1/members",
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
credential: admin,
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberGroup: model.UserGroup{
|
||||
GroupType: 1,
|
||||
LdapGroupDN: "cn=harbor_users,ou=groups,dc=example,dc=com",
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
credential: admin,
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberGroup: model.UserGroup{
|
||||
GroupType: 2,
|
||||
ID: httpUgList[0].ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
credential: admin,
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberGroup: model.UserGroup{
|
||||
GroupType: 1,
|
||||
ID: ugList[0].ID,
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusCreated,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: "/api/projects/1/members",
|
||||
credential: admin,
|
||||
bodyJSON: &models.MemberReq{
|
||||
Role: 1,
|
||||
MemberGroup: model.UserGroup{
|
||||
GroupType: 2,
|
||||
GroupName: "vsphere.local/users",
|
||||
},
|
||||
},
|
||||
},
|
||||
code: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestProjectMemberAPI_PutAndDelete(t *testing.T) {
|
||||
|
||||
userID, err := dao.Register(models.User{
|
||||
Username: "restuser",
|
||||
Password: "Harbor12345",
|
||||
Email: "restuser@example.com",
|
||||
})
|
||||
defer dao.DeleteUser(int(userID))
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when create user: %v", err)
|
||||
}
|
||||
|
||||
ID, err := project.AddProjectMember(models.Member{
|
||||
ProjectID: 1,
|
||||
Role: 1,
|
||||
EntityID: int(userID),
|
||||
EntityType: "u",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when add project member: %v", err)
|
||||
}
|
||||
|
||||
projectID, err := dao.AddProject(models.Project{Name: "memberputanddelete", OwnerID: 1})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when add project: %v", err)
|
||||
}
|
||||
defer dao.DeleteProject(projectID)
|
||||
|
||||
memberID, err := project.AddProjectMember(models.Member{
|
||||
ProjectID: projectID,
|
||||
Role: 1,
|
||||
EntityID: int(userID),
|
||||
EntityType: "u",
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when add project member: %v", err)
|
||||
}
|
||||
|
||||
URL := fmt.Sprintf("/api/projects/1/members/%v", ID)
|
||||
badURL := fmt.Sprintf("/api/projects/1/members/%v", 0)
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: URL,
|
||||
bodyJSON: &models.Member{
|
||||
Role: 2,
|
||||
},
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: URL,
|
||||
bodyJSON: &models.Member{
|
||||
Role: 2,
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: URL,
|
||||
bodyJSON: &models.Member{
|
||||
Role: 4,
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: badURL,
|
||||
bodyJSON: &models.Member{
|
||||
Role: 2,
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: URL,
|
||||
bodyJSON: &models.Member{
|
||||
Role: -2,
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("/api/projects/1/members/%v", memberID),
|
||||
bodyJSON: &models.Member{
|
||||
Role: 2,
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: URL,
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("/api/projects/1/members/%v", memberID),
|
||||
bodyJSON: &models.Member{
|
||||
Role: 2,
|
||||
},
|
||||
credential: admin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
runCodeCheckingCases(t, cases...)
|
||||
|
||||
}
|
||||
|
||||
func Test_isValidRole(t *testing.T) {
|
||||
type args struct {
|
||||
role int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want bool
|
||||
}{
|
||||
{"project admin", args{1}, true},
|
||||
{"maintainer", args{4}, true},
|
||||
{"developer", args{2}, true},
|
||||
{"guest", args{3}, true},
|
||||
{"limited guest", args{5}, true},
|
||||
{"unknow", args{6}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isValidRole(tt.args.role); got != tt.want {
|
||||
t.Errorf("isValidRole() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -14,22 +14,22 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/config"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
ugModel "github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/dao/project"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
"github.com/goharbor/harbor/src/core/api"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/member"
|
||||
memberModels "github.com/goharbor/harbor/src/pkg/member/models"
|
||||
|
||||
"github.com/goharbor/harbor/src/core/auth"
|
||||
)
|
||||
@ -267,7 +267,7 @@ func TestAuthenticateHelperOnBoardUser(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOnBoardGroup(t *testing.T) {
|
||||
group := model.UserGroup{
|
||||
group := ugModel.UserGroup{
|
||||
GroupName: "harbor_group2",
|
||||
LdapGroupDN: "cn=harbor_group2,ou=groups,dc=example,dc=com",
|
||||
}
|
||||
@ -360,18 +360,21 @@ func TestSearchAndOnBoardUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
func TestAddProjectMemberWithLdapUser(t *testing.T) {
|
||||
memberMgr := member.Mgr
|
||||
ctx := orm.Context()
|
||||
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when GetProjectByName: %v", err)
|
||||
}
|
||||
member := models.MemberReq{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
MemberUser: models.User{
|
||||
Username: "mike",
|
||||
},
|
||||
Role: common.RoleProjectAdmin,
|
||||
userID, err := auth.SearchAndOnBoardUser("mike")
|
||||
member := memberModels.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
EntityType: common.UserMember,
|
||||
Entityname: "mike",
|
||||
EntityID: userID,
|
||||
Role: common.RoleProjectAdmin,
|
||||
}
|
||||
pmid, err := api.AddProjectMember(currentProject.ProjectID, member)
|
||||
pmid, err := memberMgr.AddProjectMember(ctx, member)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddOrUpdateProjectMember: %v", err)
|
||||
}
|
||||
@ -383,14 +386,14 @@ func TestAddProjectMemberWithLdapUser(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when GetProjectByName: %v", err)
|
||||
}
|
||||
member2 := models.MemberReq{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
MemberUser: models.User{
|
||||
Username: "mike",
|
||||
},
|
||||
Role: common.RoleProjectAdmin,
|
||||
member2 := memberModels.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
EntityType: common.UserMember,
|
||||
Entityname: "mike",
|
||||
EntityID: userID,
|
||||
Role: common.RoleProjectAdmin,
|
||||
}
|
||||
pmid, err = api.AddProjectMember(currentProject.ProjectID, member2)
|
||||
pmid, err = memberMgr.AddProjectMember(ctx, member2)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddOrUpdateProjectMember: %v", err)
|
||||
}
|
||||
@ -399,30 +402,31 @@ func TestAddProjectMemberWithLdapUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
func TestAddProjectMemberWithLdapGroup(t *testing.T) {
|
||||
memberMgr := member.Mgr
|
||||
ctx := orm.Context()
|
||||
currentProject, err := dao.GetProjectByName("member_test_01")
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when GetProjectByName: %v", err)
|
||||
}
|
||||
userGroups := []model.UserGroup{{GroupName: "cn=harbor_users,ou=groups,dc=example,dc=com", LdapGroupDN: "cn=harbor_users,ou=groups,dc=example,dc=com", GroupType: common.LDAPGroupType}}
|
||||
groupIds, err := usergroup.Mgr.Populate(orm.Context(), userGroups)
|
||||
member := models.MemberReq{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
MemberGroup: model.UserGroup{
|
||||
ID: groupIds[0],
|
||||
},
|
||||
Role: common.RoleProjectAdmin,
|
||||
userGroups := []ugModel.UserGroup{{GroupName: "cn=harbor_users,ou=groups,dc=example,dc=com", LdapGroupDN: "cn=harbor_users,ou=groups,dc=example,dc=com", GroupType: common.LDAPGroupType}}
|
||||
groupIds, err := usergroup.Mgr.Populate(ctx, userGroups)
|
||||
m := memberModels.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
EntityType: common.GroupMember,
|
||||
EntityID: groupIds[0],
|
||||
Role: common.RoleProjectAdmin,
|
||||
}
|
||||
pmid, err := api.AddProjectMember(currentProject.ProjectID, member)
|
||||
pmid, err := memberMgr.AddProjectMember(ctx, m)
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred in AddOrUpdateProjectMember: %v", err)
|
||||
}
|
||||
if pmid == 0 {
|
||||
t.Errorf("Error occurred in AddOrUpdateProjectMember: pmid: %v", pmid)
|
||||
}
|
||||
queryMember := models.Member{
|
||||
queryMember := memberModels.Member{
|
||||
ProjectID: currentProject.ProjectID,
|
||||
}
|
||||
memberList, err := project.GetProjectMember(queryMember)
|
||||
memberList, err := member.Mgr.List(ctx, queryMember, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to query project member, %v, error: %v", queryMember, err)
|
||||
}
|
||||
|
@ -70,6 +70,25 @@ func QuerySetter(ctx context.Context, model interface{}, query *q.Query) (orm.Qu
|
||||
return qs, nil
|
||||
}
|
||||
|
||||
// PaginationOnRawSQL append page information to the raw sql
|
||||
// It should be called after the order by
|
||||
// e.g.
|
||||
// select a, b, c from mytable order by a limit ? offset ?
|
||||
// it appends the " limit ? offset ? " to sql,
|
||||
// and appends the limit value and offset value to the params of this query
|
||||
func PaginationOnRawSQL(query *q.Query, sql string, params []interface{}) (string, []interface{}) {
|
||||
if query != nil && query.PageSize > 0 {
|
||||
sql += ` limit ?`
|
||||
params = append(params, query.PageSize)
|
||||
|
||||
if query.PageNumber > 0 {
|
||||
sql += ` offset ?`
|
||||
params = append(params, (query.PageNumber-1)*query.PageSize)
|
||||
}
|
||||
}
|
||||
return sql, params
|
||||
}
|
||||
|
||||
// QuerySetterForCount creates the query setter used for count with the sort and pagination information ignored
|
||||
func QuerySetterForCount(ctx context.Context, model interface{}, query *q.Query, ignoredCols ...string) (orm.QuerySeter, error) {
|
||||
query = q.MustClone(query)
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
. "github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@ -410,6 +411,19 @@ func (suite *OrmSuite) TestReadOrCreateParallel() {
|
||||
suite.Equal(1, sum)
|
||||
}
|
||||
|
||||
func (suite *OrmSuite) TestPaginationOnRawSQL() {
|
||||
query := &q.Query{
|
||||
PageNumber: 1,
|
||||
PageSize: 10,
|
||||
}
|
||||
sql := "select * from harbor_user where user_id > ? order by user_name "
|
||||
params := []interface{}{2}
|
||||
sql, params = PaginationOnRawSQL(query, sql, params)
|
||||
suite.Equal("select * from harbor_user where user_id > ? order by user_name limit ? offset ?", sql)
|
||||
suite.Equal(int64(10), params[1])
|
||||
suite.Equal(int64(0), params[2])
|
||||
}
|
||||
|
||||
func TestRunOrmSuite(t *testing.T) {
|
||||
suite.Run(t, new(OrmSuite))
|
||||
}
|
||||
|
@ -10,13 +10,14 @@ import (
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/dao/project"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
proctl "github.com/goharbor/harbor/src/controller/project"
|
||||
quotactl "github.com/goharbor/harbor/src/controller/quota"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/artifact"
|
||||
"github.com/goharbor/harbor/src/pkg/member"
|
||||
memberModels "github.com/goharbor/harbor/src/pkg/member/models"
|
||||
qtypes "github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
"github.com/goharbor/harbor/src/pkg/repository"
|
||||
)
|
||||
@ -105,15 +106,17 @@ func setupTest(t *testing.T) {
|
||||
// Add member to project
|
||||
pmIDs = make([]int, 0)
|
||||
alice.UserID, bob.UserID, eve.UserID = int(aliceID), int(bobID), int(eveID)
|
||||
p1m1ID, err := project.AddProjectMember(models.Member{ProjectID: proID1, Role: common.RoleDeveloper, EntityID: int(aliceID), EntityType: common.UserMember})
|
||||
|
||||
p1m1ID, err := member.Mgr.AddProjectMember(ctx, memberModels.Member{ProjectID: proID1, Role: common.RoleDeveloper, EntityID: int(aliceID), EntityType: common.UserMember})
|
||||
if err != nil {
|
||||
t.Errorf("add project member error %v", err)
|
||||
}
|
||||
p2m1ID, err := project.AddProjectMember(models.Member{ProjectID: proID2, Role: common.RoleMaintainer, EntityID: int(bobID), EntityType: common.UserMember})
|
||||
p2m1ID, err := member.Mgr.AddProjectMember(ctx, memberModels.Member{ProjectID: proID2, Role: common.RoleMaintainer, EntityID: int(bobID), EntityType: common.UserMember})
|
||||
if err != nil {
|
||||
t.Errorf("add project member error %v", err)
|
||||
}
|
||||
p2m2ID, err := project.AddProjectMember(models.Member{ProjectID: proID2, Role: common.RoleMaintainer, EntityID: int(eveID), EntityType: common.UserMember})
|
||||
p2m2ID, err := member.Mgr.AddProjectMember(ctx, memberModels.Member{ProjectID: proID2, Role: common.RoleMaintainer, EntityID: int(eveID), EntityType: common.UserMember})
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("add project member error %v", err)
|
||||
}
|
||||
|
@ -1,36 +1,71 @@
|
||||
// Copyright Project Harbor Authors
|
||||
// Copyright 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
|
||||
// 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
|
||||
// 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.
|
||||
// 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 project
|
||||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/member/models"
|
||||
)
|
||||
|
||||
// GetProjectMember gets all members of the project.
|
||||
func GetProjectMember(queryMember models.Member) ([]*models.Member, error) {
|
||||
func init() {
|
||||
orm.RegisterModel(
|
||||
new(models.Member),
|
||||
)
|
||||
}
|
||||
|
||||
// DAO the dao for project member
|
||||
type DAO interface {
|
||||
// GetProjectMember gets all members of the project.
|
||||
GetProjectMember(ctx context.Context, queryMember models.Member, query *q.Query) ([]*models.Member, error)
|
||||
// GetTotalOfProjectMembers returns total of project members
|
||||
GetTotalOfProjectMembers(ctx context.Context, projectID int64, query *q.Query, roles ...int) (int, error)
|
||||
// AddProjectMember inserts a record to table project_member
|
||||
AddProjectMember(ctx context.Context, member models.Member) (int, error)
|
||||
// UpdateProjectMemberRole updates the record in table project_member, only role can be changed
|
||||
UpdateProjectMemberRole(ctx context.Context, projectID int64, pmID int, role int) error
|
||||
// DeleteProjectMemberByID - Delete Project Member by ID
|
||||
DeleteProjectMemberByID(ctx context.Context, projectID int64, pmid int) error
|
||||
// SearchMemberByName search members of the project by entity_name
|
||||
SearchMemberByName(ctx context.Context, projectID int64, entityName string) ([]*models.Member, error)
|
||||
// ListRoles lists the roles of user for the specific project
|
||||
ListRoles(ctx context.Context, user *models.User, projectID int64) ([]int, error)
|
||||
}
|
||||
|
||||
type dao struct {
|
||||
}
|
||||
|
||||
// New ...
|
||||
func New() DAO {
|
||||
return &dao{}
|
||||
}
|
||||
|
||||
func (d *dao) GetProjectMember(ctx context.Context, queryMember models.Member, query *q.Query) ([]*models.Member, error) {
|
||||
log.Debugf("Query condition %+v", queryMember)
|
||||
if queryMember.ProjectID == 0 {
|
||||
return nil, fmt.Errorf("Failed to query project member, query condition %v", queryMember)
|
||||
}
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o := dao.GetOrmer()
|
||||
sql := ` select a.* from (select pm.id as id, pm.project_id as project_id, ug.id as entity_id, ug.group_name as entity_name, ug.creation_time, ug.update_time, r.name as rolename,
|
||||
r.role_id as role, pm.entity_type as entity_type from user_group ug join project_member pm
|
||||
on pm.project_id = ? and ug.id = pm.entity_id join role r on pm.role = r.role_id where pm.entity_type = 'g'
|
||||
@ -65,14 +100,14 @@ func GetProjectMember(queryMember models.Member) ([]*models.Member, error) {
|
||||
queryParam = append(queryParam, queryMember.ID)
|
||||
}
|
||||
sql += ` order by entity_name `
|
||||
sql, queryParam = orm.PaginationOnRawSQL(query, sql, queryParam)
|
||||
members := []*models.Member{}
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&members)
|
||||
_, err = o.Raw(sql, queryParam).QueryRows(&members)
|
||||
|
||||
return members, err
|
||||
}
|
||||
|
||||
// GetTotalOfProjectMembers returns total of project members
|
||||
func GetTotalOfProjectMembers(projectID int64, roles ...int) (int64, error) {
|
||||
func (d *dao) GetTotalOfProjectMembers(ctx context.Context, projectID int64, query *q.Query, roles ...int) (int, error) {
|
||||
log.Debugf("Query condition %+v", projectID)
|
||||
if projectID == 0 {
|
||||
return 0, fmt.Errorf("failed to get total of project members, project id required %v", projectID)
|
||||
@ -87,16 +122,21 @@ func GetTotalOfProjectMembers(projectID int64, roles ...int) (int64, error) {
|
||||
queryParam = append(queryParam, roles[0])
|
||||
}
|
||||
|
||||
var count int64
|
||||
err := dao.GetOrmer().Raw(sql, queryParam).QueryRow(&count)
|
||||
var count int
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
o.Raw(sql, queryParam).QueryRow(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
// AddProjectMember inserts a record to table project_member
|
||||
func AddProjectMember(member models.Member) (int, error) {
|
||||
|
||||
func (d *dao) AddProjectMember(ctx context.Context, member models.Member) (int, error) {
|
||||
log.Debugf("Adding project member %+v", member)
|
||||
o := dao.GetOrmer()
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if member.EntityID <= 0 {
|
||||
return 0, fmt.Errorf("Invalid entity_id, member: %+v", member)
|
||||
@ -107,7 +147,7 @@ func AddProjectMember(member models.Member) (int, error) {
|
||||
}
|
||||
|
||||
delSQL := "delete from project_member where project_id = ? and entity_id = ? and entity_type = ? "
|
||||
_, err := o.Raw(delSQL, member.ProjectID, member.EntityID, member.EntityType).Exec()
|
||||
_, err = o.Raw(delSQL, member.ProjectID, member.EntityID, member.EntityType).Exec()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -121,27 +161,33 @@ func AddProjectMember(member models.Member) (int, error) {
|
||||
return pmid, err
|
||||
}
|
||||
|
||||
// UpdateProjectMemberRole updates the record in table project_member, only role can be changed
|
||||
func UpdateProjectMemberRole(pmID int, role int) error {
|
||||
o := dao.GetOrmer()
|
||||
sql := "update project_member set role = ? where id = ? "
|
||||
_, err := o.Raw(sql, role, pmID).Exec()
|
||||
func (d *dao) UpdateProjectMemberRole(ctx context.Context, projectID int64, pmID int, role int) error {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sql := "update project_member set role = ? where project_id = ? and id = ? "
|
||||
_, err = o.Raw(sql, role, projectID, pmID).Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteProjectMemberByID - Delete Project Member by ID
|
||||
func DeleteProjectMemberByID(pmid int) error {
|
||||
o := dao.GetOrmer()
|
||||
sql := "delete from project_member where id = ?"
|
||||
if _, err := o.Raw(sql, pmid).Exec(); err != nil {
|
||||
func (d *dao) DeleteProjectMemberByID(ctx context.Context, projectID int64, pmid int) error {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sql := "delete from project_member where project_id = ? and id = ?"
|
||||
if _, err := o.Raw(sql, projectID, pmid).Exec(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SearchMemberByName search members of the project by entity_name
|
||||
func SearchMemberByName(projectID int64, entityName string) ([]*models.Member, error) {
|
||||
o := dao.GetOrmer()
|
||||
func (d *dao) SearchMemberByName(ctx context.Context, projectID int64, entityName string) ([]*models.Member, error) {
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sql := `select pm.id, pm.project_id,
|
||||
u.username as entity_name,
|
||||
r.name as rolename,
|
||||
@ -167,12 +213,11 @@ func SearchMemberByName(projectID int64, entityName string) ([]*models.Member, e
|
||||
queryParam = append(queryParam, "%"+orm.Escape(entityName)+"%")
|
||||
members := []*models.Member{}
|
||||
log.Debugf("Query sql: %v", sql)
|
||||
_, err := o.Raw(sql, queryParam).QueryRows(&members)
|
||||
_, err = o.Raw(sql, queryParam).QueryRows(&members)
|
||||
return members, err
|
||||
}
|
||||
|
||||
// ListRoles lists the roles of user for the specific project
|
||||
func ListRoles(user *models.User, projectID int64) ([]int, error) {
|
||||
func (d *dao) ListRoles(ctx context.Context, user *models.User, projectID int64) ([]int, error) {
|
||||
if user == nil {
|
||||
return nil, nil
|
||||
}
|
||||
@ -186,12 +231,16 @@ func ListRoles(user *models.User, projectID int64) ([]int, error) {
|
||||
sql += fmt.Sprintf(`union
|
||||
select role
|
||||
from project_member
|
||||
where entity_type = 'g' and entity_id in ( %s ) and project_id = ? `, utils.ParamPlaceholderForIn(len(user.GroupIDs)))
|
||||
where entity_type = 'g' and entity_id in ( %s ) and project_id = ? `, orm.ParamPlaceholderForIn(len(user.GroupIDs)))
|
||||
params = append(params, user.GroupIDs)
|
||||
params = append(params, projectID)
|
||||
}
|
||||
roles := []int{}
|
||||
_, err := dao.GetOrmer().Raw(sql, params).QueryRows(&roles)
|
||||
o, err := orm.FromContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = o.Raw(sql, params).QueryRows(&roles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
271
src/pkg/member/dao/dao_test.go
Normal file
271
src/pkg/member/dao/dao_test.go
Normal file
@ -0,0 +1,271 @@
|
||||
// Copyright 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 dao
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
_ "github.com/goharbor/harbor/src/common/dao"
|
||||
testDao "github.com/goharbor/harbor/src/common/dao"
|
||||
comModels "github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/member/models"
|
||||
"github.com/goharbor/harbor/src/pkg/project"
|
||||
"github.com/goharbor/harbor/src/pkg/user"
|
||||
"github.com/goharbor/harbor/src/pkg/usergroup"
|
||||
ugModel "github.com/goharbor/harbor/src/pkg/usergroup/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type DaoTestSuite struct {
|
||||
htesting.Suite
|
||||
dao DAO
|
||||
projectMgr project.Manager
|
||||
projectID int64
|
||||
userMgr user.Manager
|
||||
}
|
||||
|
||||
func (s *DaoTestSuite) SetupSuite() {
|
||||
s.Suite.SetupSuite()
|
||||
s.Suite.ClearTables = []string{"project_member"}
|
||||
s.dao = New()
|
||||
// Extract to test utils
|
||||
initSqls := []string{
|
||||
"insert into harbor_user (username, email, password, realname) values ('member_test_01', 'member_test_01@example.com', '123456', 'member_test_01')",
|
||||
"insert into project (name, owner_id) values ('member_test_01', 1)",
|
||||
"insert into user_group (group_name, group_type, ldap_group_dn) values ('test_group_01', 1, 'CN=harbor_users,OU=sample,OU=vmware,DC=harbor,DC=com')",
|
||||
"update project set owner_id = (select user_id from harbor_user where username = 'member_test_01') where name = 'member_test_01'",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_01') , (select user_id from harbor_user where username = 'member_test_01'), 'u', 1)",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_01') , (select id from user_group where group_name = 'test_group_01'), 'g', 1)",
|
||||
|
||||
"insert into harbor_user (username, email, password, realname) values ('member_test_02', 'member_test_02@example.com', '123456', 'member_test_02')",
|
||||
"insert into project (name, owner_id) values ('member_test_02', 1)",
|
||||
"insert into user_group (group_name, group_type, ldap_group_dn) values ('test_group_02', 1, 'CN=harbor_users,OU=sample,OU=vmware,DC=harbor,DC=com')",
|
||||
"update project set owner_id = (select user_id from harbor_user where username = 'member_test_02') where name = 'member_test_02'",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_02') , (select user_id from harbor_user where username = 'member_test_02'), 'u', 1)",
|
||||
"insert into project_member (project_id, entity_id, entity_type, role) values ( (select project_id from project where name = 'member_test_02') , (select id from user_group where group_name = 'test_group_02'), 'g', 1)",
|
||||
}
|
||||
|
||||
clearSqls := []string{
|
||||
"delete from project where name='member_test_01' or name='member_test_02'",
|
||||
"delete from harbor_user where username='member_test_01' or username='member_test_02' or username='pm_sample'",
|
||||
"delete from user_group",
|
||||
"delete from project_member where id > 1",
|
||||
}
|
||||
testDao.PrepareTestData(clearSqls, initSqls)
|
||||
s.projectMgr = project.Mgr
|
||||
s.userMgr = user.Mgr
|
||||
ctx := s.Context()
|
||||
proj, err := s.projectMgr.Get(ctx, "member_test_01")
|
||||
s.Nil(err)
|
||||
s.NotNil(proj)
|
||||
s.projectID = proj.ProjectID
|
||||
}
|
||||
func (s *DaoTestSuite) TearDownSuite() {
|
||||
}
|
||||
|
||||
func (s *DaoTestSuite) TestAddProjectMember() {
|
||||
ctx := s.Context()
|
||||
proj, err := s.projectMgr.Get(ctx, "member_test_01")
|
||||
s.Nil(err)
|
||||
s.NotNil(proj)
|
||||
|
||||
member := models.Member{
|
||||
ProjectID: proj.ProjectID,
|
||||
EntityID: 1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
}
|
||||
pmid, err := s.dao.AddProjectMember(ctx, member)
|
||||
s.Nil(err)
|
||||
s.True(pmid > 0)
|
||||
|
||||
queryMember := models.Member{
|
||||
ProjectID: proj.ProjectID,
|
||||
ID: pmid,
|
||||
}
|
||||
memberList, err := s.dao.GetProjectMember(ctx, queryMember, nil)
|
||||
s.Nil(err)
|
||||
s.False(len(memberList) == 0)
|
||||
|
||||
_, err = s.dao.AddProjectMember(ctx, models.Member{
|
||||
ProjectID: -1,
|
||||
EntityID: 1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
})
|
||||
|
||||
s.NotNil(err)
|
||||
|
||||
_, err = s.dao.AddProjectMember(ctx, models.Member{
|
||||
ProjectID: 1,
|
||||
EntityID: -1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
})
|
||||
|
||||
s.NotNil(err)
|
||||
}
|
||||
|
||||
func (s *DaoTestSuite) TestUpdateProjectMemberRole() {
|
||||
ctx := s.Context()
|
||||
proj, err := s.projectMgr.Get(ctx, "member_test_01")
|
||||
s.Nil(err)
|
||||
s.NotNil(proj)
|
||||
user := comModels.User{
|
||||
Username: "pm_sample",
|
||||
Email: "pm_sample@example.com",
|
||||
Realname: "pm_sample",
|
||||
Password: "1234567d",
|
||||
}
|
||||
o, err := orm.FromContext(ctx)
|
||||
s.Nil(err)
|
||||
userID, err := o.Insert(&user)
|
||||
s.Nil(err)
|
||||
member := models.Member{
|
||||
ProjectID: proj.ProjectID,
|
||||
EntityID: int(userID),
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleProjectAdmin,
|
||||
}
|
||||
|
||||
pmid, err := s.dao.AddProjectMember(ctx, member)
|
||||
s.Nil(err)
|
||||
s.dao.UpdateProjectMemberRole(ctx, proj.ProjectID, pmid, common.RoleDeveloper)
|
||||
|
||||
queryMember := models.Member{
|
||||
ProjectID: proj.ProjectID,
|
||||
EntityID: int(userID),
|
||||
EntityType: common.UserMember,
|
||||
}
|
||||
|
||||
memberList, err := s.dao.GetProjectMember(ctx, queryMember, nil)
|
||||
s.Nil(err)
|
||||
s.True(len(memberList) == 1, "project member should exist")
|
||||
memberItem := memberList[0]
|
||||
s.Equal(common.RoleDeveloper, memberItem.Role, "should be developer role")
|
||||
s.Equal(user.Username, memberItem.Entityname)
|
||||
|
||||
memberList2, err := s.dao.SearchMemberByName(ctx, proj.ProjectID, "pm_sample")
|
||||
s.Nil(err)
|
||||
s.True(len(memberList2) > 0)
|
||||
|
||||
memberList3, err := s.dao.SearchMemberByName(ctx, proj.ProjectID, "")
|
||||
s.Nil(err)
|
||||
s.True(len(memberList3) > 0, "failed to search project member")
|
||||
}
|
||||
|
||||
func (s *DaoTestSuite) TestGetProjectMembers() {
|
||||
ctx := s.Context()
|
||||
|
||||
query1 := models.Member{ProjectID: s.projectID, Entityname: "member_test_01", EntityType: common.UserMember}
|
||||
member1, err := s.dao.GetProjectMember(ctx, query1, nil)
|
||||
s.Nil(err)
|
||||
s.True(len(member1) > 0)
|
||||
s.Equal(member1[0].Entityname, "member_test_01")
|
||||
|
||||
query2 := models.Member{ProjectID: s.projectID, Entityname: "test_group_01", EntityType: common.GroupMember}
|
||||
member2, err := s.dao.GetProjectMember(ctx, query2, nil)
|
||||
s.Nil(err)
|
||||
s.True(len(member2) > 0)
|
||||
s.Equal(member2[0].Entityname, "test_group_01")
|
||||
}
|
||||
|
||||
func (s *DaoTestSuite) TestGetTotalOfProjectMembers() {
|
||||
ctx := s.Context()
|
||||
tot, err := s.dao.GetTotalOfProjectMembers(ctx, s.projectID, nil)
|
||||
s.Nil(err)
|
||||
s.Equal(2, int(tot))
|
||||
}
|
||||
|
||||
func (s *DaoTestSuite) TestListRoles() {
|
||||
ctx := s.Context()
|
||||
|
||||
// nil user
|
||||
roles, err := s.dao.ListRoles(ctx, nil, 1)
|
||||
s.Nil(err)
|
||||
s.Len(roles, 0)
|
||||
|
||||
// user with empty groups
|
||||
u, err := s.userMgr.GetByName(ctx, "member_test_01")
|
||||
s.Nil(err)
|
||||
s.NotNil(u)
|
||||
user := &models.User{
|
||||
UserID: u.UserID,
|
||||
Username: u.Username,
|
||||
}
|
||||
roles, err = s.dao.ListRoles(ctx, user, s.projectID)
|
||||
s.Nil(err)
|
||||
s.Len(roles, 1)
|
||||
|
||||
// user with a group whose ID doesn't exist
|
||||
user.GroupIDs = []int{9999}
|
||||
roles, err = s.dao.ListRoles(ctx, user, s.projectID)
|
||||
s.Nil(err)
|
||||
s.Len(roles, 1)
|
||||
s.Equal(common.RoleProjectAdmin, roles[0])
|
||||
|
||||
// user with a valid group
|
||||
groupID, err := usergroup.Mgr.Create(ctx, ugModel.UserGroup{
|
||||
GroupName: "group_for_list_role",
|
||||
GroupType: 1,
|
||||
LdapGroupDN: "CN=list_role_users,OU=sample,OU=vmware,DC=harbor,DC=com",
|
||||
})
|
||||
|
||||
s.Nil(err)
|
||||
defer usergroup.Mgr.Delete(ctx, groupID)
|
||||
|
||||
memberID, err := s.dao.AddProjectMember(ctx, models.Member{
|
||||
ProjectID: s.projectID,
|
||||
Role: common.RoleDeveloper,
|
||||
EntityID: groupID,
|
||||
EntityType: "g",
|
||||
})
|
||||
s.Nil(err)
|
||||
defer s.dao.DeleteProjectMemberByID(ctx, s.projectID, memberID)
|
||||
|
||||
user.GroupIDs = []int{groupID}
|
||||
roles, err = s.dao.ListRoles(ctx, user, s.projectID)
|
||||
s.Nil(err)
|
||||
s.Len(roles, 2)
|
||||
s.Equal(common.RoleProjectAdmin, roles[0])
|
||||
s.Equal(common.RoleDeveloper, roles[1])
|
||||
}
|
||||
|
||||
func (s *DaoTestSuite) TestDeleteProjectMember() {
|
||||
ctx := s.Context()
|
||||
var addMember = models.Member{
|
||||
ProjectID: s.projectID,
|
||||
EntityID: 1,
|
||||
EntityType: common.UserMember,
|
||||
Role: common.RoleDeveloper,
|
||||
}
|
||||
pmid, err := s.dao.AddProjectMember(ctx, addMember)
|
||||
s.Nil(err)
|
||||
s.True(pmid > 0)
|
||||
|
||||
err = s.dao.DeleteProjectMemberByID(ctx, s.projectID, pmid)
|
||||
s.Nil(err)
|
||||
|
||||
// not exist
|
||||
err = s.dao.DeleteProjectMemberByID(ctx, s.projectID, -1)
|
||||
s.Nil(err)
|
||||
|
||||
}
|
||||
|
||||
func TestDaoTestSuite(t *testing.T) {
|
||||
suite.Run(t, &DaoTestSuite{})
|
||||
}
|
101
src/pkg/member/manager.go
Normal file
101
src/pkg/member/manager.go
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright 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 member
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/member/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/member/models"
|
||||
)
|
||||
|
||||
var (
|
||||
// Mgr default project member manager
|
||||
Mgr = NewManager()
|
||||
)
|
||||
|
||||
// Manager is used to manage the project member
|
||||
type Manager interface {
|
||||
// AddProjectMember add project member
|
||||
AddProjectMember(ctx context.Context, member models.Member) (int, error)
|
||||
// Delete delete project member
|
||||
Delete(ctx context.Context, projectID int64, memberID int) error
|
||||
// Get get project member by ID
|
||||
Get(ctx context.Context, projectID int64, memberID int) (*models.Member, error)
|
||||
// List list the project member by conditions
|
||||
List(ctx context.Context, queryMember models.Member, query *q.Query) ([]*models.Member, error)
|
||||
// UpdateRole update project member's role
|
||||
UpdateRole(ctx context.Context, projectID int64, pmID int, role int) error
|
||||
// SearchMemberByName search project member by name
|
||||
SearchMemberByName(ctx context.Context, projectID int64, entityName string) ([]*models.Member, error)
|
||||
// GetTotalOfProjectMembers get the total amount of project members
|
||||
GetTotalOfProjectMembers(ctx context.Context, projectID int64, query *q.Query, roles ...int) (int, error)
|
||||
// ListRoles list project roles
|
||||
ListRoles(ctx context.Context, user *models.User, projectID int64) ([]int, error)
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
dao dao.DAO
|
||||
}
|
||||
|
||||
func (m *manager) Get(ctx context.Context, projectID int64, memberID int) (*models.Member, error) {
|
||||
query := models.Member{
|
||||
ID: memberID,
|
||||
ProjectID: projectID,
|
||||
}
|
||||
pm, err := m.dao.GetProjectMember(ctx, query, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(pm) == 0 {
|
||||
return nil, errors.NotFoundError(nil).
|
||||
WithMessage("the project member is not found, project id %v, member id %v", projectID, memberID)
|
||||
}
|
||||
return pm[0], nil
|
||||
}
|
||||
|
||||
func (m *manager) AddProjectMember(ctx context.Context, member models.Member) (int, error) {
|
||||
return m.dao.AddProjectMember(ctx, member)
|
||||
}
|
||||
|
||||
func (m *manager) UpdateRole(ctx context.Context, projectID int64, pmID int, role int) error {
|
||||
return m.dao.UpdateProjectMemberRole(ctx, projectID, pmID, role)
|
||||
}
|
||||
|
||||
func (m *manager) SearchMemberByName(ctx context.Context, projectID int64, entityName string) ([]*models.Member, error) {
|
||||
return m.dao.SearchMemberByName(ctx, projectID, entityName)
|
||||
}
|
||||
|
||||
func (m *manager) GetTotalOfProjectMembers(ctx context.Context, projectID int64, query *q.Query, roles ...int) (int, error) {
|
||||
return m.dao.GetTotalOfProjectMembers(ctx, projectID, query, roles...)
|
||||
}
|
||||
|
||||
func (m *manager) ListRoles(ctx context.Context, user *models.User, projectID int64) ([]int, error) {
|
||||
return m.dao.ListRoles(ctx, user, projectID)
|
||||
}
|
||||
|
||||
func (m *manager) List(ctx context.Context, queryMember models.Member, query *q.Query) ([]*models.Member, error) {
|
||||
return m.dao.GetProjectMember(ctx, queryMember, query)
|
||||
}
|
||||
|
||||
func (m *manager) Delete(ctx context.Context, projectID int64, memberID int) error {
|
||||
return m.dao.DeleteProjectMemberByID(ctx, projectID, memberID)
|
||||
}
|
||||
|
||||
// NewManager ...
|
||||
func NewManager() Manager {
|
||||
return &manager{dao: dao.New()}
|
||||
}
|
33
src/pkg/member/models/member.go
Normal file
33
src/pkg/member/models/member.go
Normal file
@ -0,0 +1,33 @@
|
||||
// Copyright 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 models
|
||||
|
||||
// Member holds the details of a member.
|
||||
type Member struct {
|
||||
ID int `orm:"pk;column(id)" json:"id"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
Entityname string `orm:"column(entity_name)" json:"entity_name"`
|
||||
Rolename string `json:"role_name"`
|
||||
Role int `json:"role_id"`
|
||||
EntityID int `orm:"column(entity_id)" json:"entity_id"`
|
||||
EntityType string `orm:"column(entity_type)" json:"entity_type"`
|
||||
}
|
||||
|
||||
// User ...
|
||||
type User struct {
|
||||
UserID int
|
||||
Username string
|
||||
GroupIDs []int
|
||||
}
|
@ -39,6 +39,7 @@ func New() http.Handler {
|
||||
ScanAllAPI: newScanAllAPI(),
|
||||
SearchAPI: newSearchAPI(),
|
||||
ProjectAPI: newProjectAPI(),
|
||||
MemberAPI: newMemberAPI(),
|
||||
PreheatAPI: newPreheatAPI(),
|
||||
IconAPI: newIconAPI(),
|
||||
RobotAPI: newRobotAPI(),
|
||||
|
171
src/server/v2.0/handler/member.go
Normal file
171
src/server/v2.0/handler/member.go
Normal file
@ -0,0 +1,171 @@
|
||||
// Copyright 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 handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/member"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
memberModels "github.com/goharbor/harbor/src/pkg/member/models"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/member"
|
||||
)
|
||||
|
||||
type memberAPI struct {
|
||||
BaseAPI
|
||||
ctl member.Controller
|
||||
}
|
||||
|
||||
func newMemberAPI() *memberAPI {
|
||||
return &memberAPI{ctl: member.NewController()}
|
||||
}
|
||||
|
||||
func (m *memberAPI) CreateProjectMember(ctx context.Context, params operation.CreateProjectMemberParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := m.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionCreate, rbac.ResourceMember); err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
if params.ProjectMember == nil {
|
||||
return m.SendError(ctx, errors.BadRequestError(nil).WithMessage("the project member should provide"))
|
||||
}
|
||||
req, err := toMemberReq(params.ProjectMember)
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
id, err := m.ctl.Create(ctx, projectNameOrID, *req)
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewCreateProjectMemberCreated().
|
||||
WithLocation(fmt.Sprintf("/api/v2.0/projects/%v/members/%d", projectNameOrID, id))
|
||||
}
|
||||
|
||||
func toMemberReq(memberReq *models.ProjectMember) (*member.Request, error) {
|
||||
data, err := json.Marshal(memberReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result member.Request
|
||||
err = json.Unmarshal(data, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func (m *memberAPI) DeleteProjectMember(ctx context.Context, params operation.DeleteProjectMemberParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := m.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionDelete, rbac.ResourceMember); err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
if params.Mid == 0 {
|
||||
return m.SendError(ctx, errors.BadRequestError(nil).WithMessage("the project member id is required."))
|
||||
}
|
||||
err := m.ctl.Delete(ctx, projectNameOrID, int(params.Mid))
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewDeleteProjectMemberOK()
|
||||
}
|
||||
|
||||
func (m *memberAPI) GetProjectMember(ctx context.Context, params operation.GetProjectMemberParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := m.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionRead, rbac.ResourceMember); err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if params.Mid == 0 {
|
||||
return m.SendError(ctx, errors.BadRequestError(nil).WithMessage("the member id can not be empty!"))
|
||||
}
|
||||
|
||||
member, err := m.ctl.Get(ctx, projectNameOrID, int(params.Mid))
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewGetProjectMemberOK().WithPayload(toProjectMemberResp(member))
|
||||
}
|
||||
|
||||
func (m *memberAPI) ListProjectMembers(ctx context.Context, params operation.ListProjectMembersParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := m.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionList, rbac.ResourceMember); err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
entityName := lib.StringValue(params.Entityname)
|
||||
query, err := m.BuildQuery(ctx, nil, nil, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
total, err := m.ctl.Count(ctx, projectNameOrID, query)
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
if total == 0 {
|
||||
return operation.NewListProjectMembersOK().
|
||||
WithXTotalCount(0).
|
||||
WithPayload([]*models.ProjectMemberEntity{})
|
||||
}
|
||||
members, err := m.ctl.List(ctx, projectNameOrID, entityName, query)
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewListProjectMembersOK().
|
||||
WithXTotalCount(int64(total)).
|
||||
WithLink(m.Links(ctx, params.HTTPRequest.URL, int64(total), query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(toProjectMemberRespList(members))
|
||||
}
|
||||
|
||||
func toProjectMemberRespList(members []*memberModels.Member) []*models.ProjectMemberEntity {
|
||||
result := make([]*models.ProjectMemberEntity, 0)
|
||||
for _, mem := range members {
|
||||
result = append(result, toProjectMemberResp(mem))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func toProjectMemberResp(member *memberModels.Member) *models.ProjectMemberEntity {
|
||||
return &models.ProjectMemberEntity{
|
||||
ProjectID: member.ProjectID,
|
||||
ID: int64(member.ID),
|
||||
EntityName: member.Entityname,
|
||||
EntityID: int64(member.EntityID),
|
||||
EntityType: member.EntityType,
|
||||
RoleID: int64(member.Role),
|
||||
RoleName: member.Rolename,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memberAPI) UpdateProjectMember(ctx context.Context, params operation.UpdateProjectMemberParams) middleware.Responder {
|
||||
projectNameOrID := parseProjectNameOrID(params.ProjectNameOrID, params.XIsResourceName)
|
||||
if err := m.RequireProjectAccess(ctx, projectNameOrID, rbac.ActionUpdate, rbac.ResourceMember); err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
if params.Role == nil {
|
||||
return m.SendError(ctx, errors.BadRequestError(nil).WithMessage("role can not be empty!"))
|
||||
}
|
||||
if params.Mid == 0 {
|
||||
return m.SendError(ctx, errors.BadRequestError(nil).WithMessage("member id can not be empty!"))
|
||||
}
|
||||
|
||||
err := m.ctl.UpdateRole(ctx, projectNameOrID, int(params.Mid), int(params.Role.RoleID))
|
||||
if err != nil {
|
||||
return m.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewUpdateProjectMemberOK()
|
||||
}
|
@ -17,15 +17,16 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/controller/config"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/controller/config"
|
||||
"github.com/goharbor/harbor/src/pkg/member"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
pro "github.com/goharbor/harbor/src/common/dao/project"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
"github.com/goharbor/harbor/src/common/security/local"
|
||||
@ -63,6 +64,7 @@ func newProjectAPI() *projectAPI {
|
||||
userMgr: user.Mgr,
|
||||
repositoryCtl: repository.Ctl,
|
||||
projectCtl: project.Ctl,
|
||||
memberMgr: member.Mgr,
|
||||
quotaCtl: quota.Ctl,
|
||||
robotMgr: robot.Mgr,
|
||||
preheatCtl: preheat.Ctl,
|
||||
@ -78,6 +80,7 @@ type projectAPI struct {
|
||||
userMgr user.Manager
|
||||
repositoryCtl repository.Controller
|
||||
projectCtl project.Controller
|
||||
memberMgr member.Manager
|
||||
quotaCtl quota.Controller
|
||||
robotMgr robot.Manager
|
||||
preheatCtl preheat.Controller
|
||||
@ -343,7 +346,7 @@ func (a *projectAPI) GetProjectSummary(ctx context.Context, params operation.Get
|
||||
}
|
||||
|
||||
if hasPerm := a.HasProjectPermission(ctx, p.ProjectID, rbac.ActionList, rbac.ResourceMember); hasPerm {
|
||||
fetchSummaries = append(fetchSummaries, getProjectMemberSummary)
|
||||
fetchSummaries = append(fetchSummaries, a.getProjectMemberSummary)
|
||||
}
|
||||
|
||||
if p.IsProxy() {
|
||||
@ -696,7 +699,7 @@ func getProjectQuotaSummary(ctx context.Context, p *project.Project, summary *mo
|
||||
}
|
||||
}
|
||||
|
||||
func getProjectMemberSummary(ctx context.Context, p *project.Project, summary *models.ProjectSummary) {
|
||||
func (a *projectAPI) getProjectMemberSummary(ctx context.Context, p *project.Project, summary *models.ProjectSummary) {
|
||||
var wg sync.WaitGroup
|
||||
|
||||
for _, e := range []struct {
|
||||
@ -712,14 +715,13 @@ func getProjectMemberSummary(ctx context.Context, p *project.Project, summary *m
|
||||
wg.Add(1)
|
||||
go func(role int, count *int64) {
|
||||
defer wg.Done()
|
||||
|
||||
total, err := pro.GetTotalOfProjectMembers(p.ProjectID, role)
|
||||
total, err := a.memberMgr.GetTotalOfProjectMembers(orm.Clone(ctx), p.ProjectID, nil, role)
|
||||
if err != nil {
|
||||
log.Warningf("failed to get total of project members of role %d", role)
|
||||
return
|
||||
}
|
||||
|
||||
*count = total
|
||||
*count = int64(total)
|
||||
}(e.role, e.count)
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ import (
|
||||
// RegisterRoutes for Harbor legacy APIs
|
||||
func registerLegacyRoutes() {
|
||||
version := APIVersion
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/members/?:pmid([0-9]+)", &api.ProjectMemberAPI{})
|
||||
beego.Router("/api/"+version+"/email/ping", &api.EmailAPI{}, "post:Ping")
|
||||
beego.Router("/api/"+version+"/health", &api.HealthAPI{}, "get:CheckHealth")
|
||||
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get")
|
||||
|
@ -30,8 +30,8 @@ def get_endpoint():
|
||||
def _create_client(server, credential, debug, api_type="products"):
|
||||
cfg = None
|
||||
if api_type in ('projectv2', 'artifact', 'repository', 'scanner', 'scan', 'scanall', 'preheat', 'quota',
|
||||
'replication', 'registry', 'robot', 'gc', 'retention', "immutable", "system_cve_allowlist",
|
||||
"configure", "users"):
|
||||
'replication', 'registry', 'robot', 'gc', 'retention', 'immutable', 'system_cve_allowlist',
|
||||
'configure', 'users', 'member'):
|
||||
cfg = v2_swagger_client.Configuration()
|
||||
else:
|
||||
cfg = swagger_client.Configuration()
|
||||
@ -73,6 +73,7 @@ def _create_client(server, credential, debug, api_type="products"):
|
||||
"system_cve_allowlist": v2_swagger_client.SystemCVEAllowlistApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"configure": v2_swagger_client.ConfigureApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"users": v2_swagger_client.UsersApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"member": v2_swagger_client.MemberApi(v2_swagger_client.ApiClient(cfg)),
|
||||
}.get(api_type,'Error: Wrong API type')
|
||||
|
||||
def _assert_status_code(expect_code, return_code, err_msg = r"HTTPS status code s not as we expected. Expected {}, while actual HTTPS status code is {}."):
|
||||
|
@ -1,10 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import base
|
||||
import swagger_client
|
||||
import v2_swagger_client
|
||||
from v2_swagger_client.rest import ApiException
|
||||
from library.base import _assert_status_code
|
||||
from v2_swagger_client.rest import ApiException
|
||||
|
||||
import base
|
||||
|
||||
|
||||
def is_member_exist_in_project(members, member_user_name, expected_member_role_id = None):
|
||||
result = False
|
||||
@ -125,15 +127,15 @@ class Project(base.Base):
|
||||
return count
|
||||
|
||||
def get_project_members(self, project_id, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
return self._get_client(**kwargs).projects_project_id_members_get(project_id)
|
||||
kwargs['api_type'] = 'member'
|
||||
return self._get_client(**kwargs).list_project_members(project_id)
|
||||
|
||||
def get_project_member(self, project_id, member_id, expect_status_code = 200, expect_response_body = None, **kwargs):
|
||||
from swagger_client.rest import ApiException
|
||||
kwargs['api_type'] = 'products'
|
||||
kwargs['api_type'] = 'member'
|
||||
data = []
|
||||
try:
|
||||
data, status_code, _ = self._get_client(**kwargs).projects_project_id_members_mid_get_with_http_info(project_id, member_id,)
|
||||
data, status_code, _ = self._get_client(**kwargs).get_project_member_with_http_info(project_id, member_id,)
|
||||
except ApiException as e:
|
||||
base._assert_status_code(expect_status_code, e.status)
|
||||
if expect_response_body is not None:
|
||||
@ -145,7 +147,7 @@ class Project(base.Base):
|
||||
return data
|
||||
|
||||
def get_project_member_id(self, project_id, member_user_name, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
kwargs['api_type'] = 'member'
|
||||
members = self.get_project_members(project_id, **kwargs)
|
||||
result = get_member_id_by_name(list(members), member_user_name)
|
||||
if result == None:
|
||||
@ -154,36 +156,36 @@ class Project(base.Base):
|
||||
return result
|
||||
|
||||
def check_project_member_not_exist(self, project_id, member_user_name, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
kwargs['api_type'] = 'member'
|
||||
members = self.get_project_members(project_id, **kwargs)
|
||||
result = is_member_exist_in_project(list(members), member_user_name)
|
||||
if result == True:
|
||||
raise Exception(r"User {} should not be a member of project with ID {}.".format(member_user_name, project_id))
|
||||
|
||||
def check_project_members_exist(self, project_id, member_user_name, expected_member_role_id = None, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
kwargs['api_type'] = 'member'
|
||||
members = self.get_project_members(project_id, **kwargs)
|
||||
result = is_member_exist_in_project(members, member_user_name, expected_member_role_id = expected_member_role_id)
|
||||
if result == False:
|
||||
raise Exception(r"User {} should be a member of project with ID {}.".format(member_user_name, project_id))
|
||||
|
||||
def update_project_member_role(self, project_id, member_id, member_role_id, expect_status_code = 200, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
kwargs['api_type'] = 'member'
|
||||
role = swagger_client.Role(role_id = member_role_id)
|
||||
data, status_code, _ = self._get_client(**kwargs).projects_project_id_members_mid_put_with_http_info(project_id, member_id, role = role)
|
||||
data, status_code, _ = self._get_client(**kwargs).update_project_member_with_http_info(project_id, member_id, role = role)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(200, status_code)
|
||||
return data
|
||||
|
||||
def delete_project_member(self, project_id, member_id, expect_status_code = 200, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
_, status_code, _ = self._get_client(**kwargs).projects_project_id_members_mid_delete_with_http_info(project_id, member_id)
|
||||
kwargs['api_type'] = 'member'
|
||||
_, status_code, _ = self._get_client(**kwargs).delete_project_member_with_http_info(project_id, member_id)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(200, status_code)
|
||||
|
||||
def add_project_members(self, project_id, user_id = None, member_role_id = None, _ldap_group_dn=None, expect_status_code = 201, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
projectMember = swagger_client.ProjectMember()
|
||||
kwargs['api_type'] = 'member'
|
||||
projectMember = v2_swagger_client.ProjectMember()
|
||||
if user_id is not None:
|
||||
projectMember.member_user = {"user_id": int(user_id)}
|
||||
if member_role_id is None:
|
||||
@ -191,12 +193,12 @@ class Project(base.Base):
|
||||
else:
|
||||
projectMember.role_id = member_role_id
|
||||
if _ldap_group_dn is not None:
|
||||
projectMember.member_group = swagger_client.UserGroup(ldap_group_dn=_ldap_group_dn)
|
||||
projectMember.member_group = v2_swagger_client.UserGroup(ldap_group_dn=_ldap_group_dn)
|
||||
|
||||
data = []
|
||||
try:
|
||||
data, status_code, header = self._get_client(**kwargs).projects_project_id_members_post_with_http_info(project_id, project_member = projectMember)
|
||||
except swagger_client.rest.ApiException as e:
|
||||
data, status_code, header = self._get_client(**kwargs).create_project_member_with_http_info(project_id, project_member = projectMember)
|
||||
except ApiException as e:
|
||||
base._assert_status_code(expect_status_code, e.status)
|
||||
else:
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
|
Loading…
Reference in New Issue
Block a user