Remove "GetMyProjects" and "GetProjectRoles" in the interface "security.Context"

Fixes #11125, remove "GetMyProjects" and "GetProjectRoles" in the interface "security.Context"

Signed-off-by: Wenkai Yin <yinw@vmware.com>
This commit is contained in:
Wenkai Yin 2020-04-01 15:31:28 +08:00
parent 83f46869d5
commit 0a372a85eb
30 changed files with 269 additions and 573 deletions

View File

@ -399,22 +399,6 @@ func TestGetProjectById(t *testing.T) {
}
}
func TestGetUserProjectRoles(t *testing.T) {
r, err := GetUserProjectRoles(currentUser.UserID, currentProject.ProjectID, common.UserMember)
if err != nil {
t.Errorf("Error happened in GetUserProjectRole: %v, userID: %+v, project Id: %d", err, currentUser.UserID, currentProject.ProjectID)
}
// Get the size of current user project role.
if len(r) != 1 {
t.Errorf("The user, id: %d, should only have one role in project, id: %d, but actual: %d", currentUser.UserID, currentProject.ProjectID, len(r))
}
if r[0].Name != "projectAdmin" {
t.Errorf("the expected rolename is: projectAdmin, actual: %s", r[0].Name)
}
}
func TestGetTotalOfProjects(t *testing.T) {
total, err := GetTotalOfProjects(nil)
if err != nil {
@ -439,23 +423,6 @@ func TestGetProjects(t *testing.T) {
}
}
func TestGetRoleByID(t *testing.T) {
r, err := GetRoleByID(models.PROJECTADMIN)
if err != nil {
t.Errorf("Failed to call GetRoleByID: %v", err)
}
if r == nil || r.Name != "projectAdmin" || r.RoleCode != "MDRWS" {
t.Errorf("Role does not match for role id: %d, actual: %+v", models.PROJECTADMIN, r)
}
r, err = GetRoleByID(9999)
if err != nil {
t.Errorf("Failed to call GetRoleByID: %v", err)
}
if r != nil {
t.Errorf("Role should nil for non-exist id 9999, actual: %+v", r)
}
}
// isAdminRole returns whether the user is admin.
func isAdminRole(userIDOrUsername interface{}) (bool, error) {
u := models.User{}

View File

@ -394,57 +394,6 @@ func TestGetTotalGroupProjects(t *testing.T) {
})
}
}
func TestGetRolesByLDAPGroup(t *testing.T) {
userGroupList, err := QueryUserGroup(models.UserGroup{LdapGroupDN: "cn=harbor_users,ou=sample,ou=vmware,dc=harbor,dc=com", GroupType: 1})
if err != nil || len(userGroupList) < 1 {
t.Errorf("failed to query user group, err %v", err)
}
userGroups := []models.UserGroup{
{GroupName: "test_http_group", GroupType: common.HTTPGroupType},
{GroupName: "test_myhttp_group", GroupType: common.HTTPGroupType},
}
gl2, err2 := PopulateGroup(userGroups)
if err2 != nil || len(gl2) != 2 {
t.Errorf("failed to query http user group, err %v", err)
}
project, err := dao.GetProjectByName("member_test_01")
if err != nil {
t.Errorf("Error occurred when Get project by name: %v", err)
}
privateProject, err := dao.GetProjectByName("group_project_private")
if err != nil {
t.Errorf("Error occurred when Get project by name: %v", err)
}
type args struct {
projectID int64
groupIDs []int
}
tests := []struct {
name string
args args
wantSize int
wantErr bool
}{
{"Check normal", args{projectID: project.ProjectID, groupIDs: []int{userGroupList[0].ID, gl2[0], gl2[1]}}, 2, false},
{"Check non exist", args{projectID: privateProject.ProjectID, groupIDs: []int{9999}}, 0, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := dao.GetRolesByGroupID(tt.args.projectID, tt.args.groupIDs)
if (err != nil) != tt.wantErr {
t.Errorf("TestGetRolesByLDAPGroup() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != tt.wantSize {
t.Errorf("TestGetRolesByLDAPGroup() = %v, want %v", len(got), tt.wantSize)
}
})
}
}
func TestSyncGroupByGroupKey(t *testing.T) {
type args []models.UserGroup

View File

@ -39,7 +39,7 @@ func AddProject(project models.Project) (int64, error) {
pmID, err := addProjectMember(models.Member{
ProjectID: projectID,
EntityID: project.OwnerID,
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
EntityType: common.UserMember,
})
if err != nil {
@ -294,25 +294,3 @@ func DeleteProject(id int64) error {
_, err = GetOrmer().Raw(sql, name, id).Exec()
return err
}
// GetRolesByGroupID - Get Project roles of the
// specified group is a member of current project
func GetRolesByGroupID(projectID int64, groupIDs []int) ([]int, error) {
var roles []int
if len(groupIDs) == 0 {
return roles, nil
}
groupIDCondition := JoinNumberConditions(groupIDs)
o := GetOrmer()
sql := fmt.Sprintf(
`select distinct pm.role from project_member pm
left join user_group ug on pm.entity_type = 'g' and pm.entity_id = ug.id
where ug.id in ( %s ) and pm.project_id = ?`,
groupIDCondition)
log.Debugf("sql for GetRolesByGroupID(project ID: %d, group ids: %v):%v", projectID, groupIDs, sql)
if _, err := o.Raw(sql, projectID).QueryRows(&roles); err != nil {
log.Warningf("Error in GetRolesByGroupID, error: %v", err)
return nil, err
}
return roles, nil
}

View File

@ -16,7 +16,6 @@ package project
import (
"fmt"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/lib/log"
@ -169,3 +168,30 @@ func SearchMemberByName(projectID int64, entityName string) ([]*models.Member, e
_, 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) {
if user == nil {
return nil, nil
}
var params []interface{}
sql := `
select role
from project_member
where entity_type = 'u' and entity_id = ? and project_id = ? `
params = append(params, user.UserID, projectID)
if len(user.GroupIDs) > 0 {
sql += fmt.Sprintf(`union
select role
from project_member
where entity_type = 'g' and entity_id in ( %s ) and project_id = ? `, dao.ParamPlaceholderForIn(len(user.GroupIDs)))
params = append(params, user.GroupIDs)
params = append(params, projectID)
}
roles := []int{}
_, err := dao.GetOrmer().Raw(sql, params).QueryRows(&roles)
if err != nil {
return nil, err
}
return roles, nil
}

View File

@ -16,6 +16,9 @@ package project
import (
"fmt"
"github.com/goharbor/harbor/src/common/dao/group"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"os"
"testing"
@ -89,7 +92,7 @@ func TestDeleteProjectMemberByID(t *testing.T) {
ProjectID: currentProject.ProjectID,
EntityID: 1,
EntityType: common.UserMember,
Role: models.DEVELOPER,
Role: common.RoleDeveloper,
}
pmid, err := AddProjectMember(addMember)
@ -129,7 +132,7 @@ func TestAddProjectMember(t *testing.T) {
ProjectID: currentProject.ProjectID,
EntityID: 1,
EntityType: common.UserMember,
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
}
log.Debugf("Current project id %v", currentProject.ProjectID)
@ -159,7 +162,7 @@ func TestAddProjectMember(t *testing.T) {
ProjectID: -1,
EntityID: 1,
EntityType: common.UserMember,
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
})
if err == nil {
t.Fatal("Should failed with negative projectID")
@ -168,7 +171,7 @@ func TestAddProjectMember(t *testing.T) {
ProjectID: 1,
EntityID: -1,
EntityType: common.UserMember,
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
})
if err == nil {
t.Fatal("Should failed with negative entityID")
@ -191,7 +194,7 @@ func TestUpdateProjectMemberRole(t *testing.T) {
ProjectID: currentProject.ProjectID,
EntityID: int(userID),
EntityType: common.UserMember,
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
}
pmid, err := AddProjectMember(member)
@ -199,7 +202,7 @@ func TestUpdateProjectMemberRole(t *testing.T) {
t.Errorf("Error occurred in UpdateProjectMember: %v", err)
}
UpdateProjectMemberRole(pmid, models.DEVELOPER)
UpdateProjectMemberRole(pmid, common.RoleDeveloper)
queryMember := models.Member{
ProjectID: currentProject.ProjectID,
@ -215,7 +218,7 @@ func TestUpdateProjectMemberRole(t *testing.T) {
t.Errorf("Error occurred in Failed, size: %d, condition:%+v", len(memberList), queryMember)
}
memberItem := memberList[0]
if memberItem.Role != models.DEVELOPER || memberItem.Entityname != user.Username {
if memberItem.Role != common.RoleDeveloper || memberItem.Entityname != user.Username {
t.Errorf("member doesn't match!")
}
@ -325,6 +328,55 @@ func TestGetTotalOfProjectMembers(t *testing.T) {
}
}
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])
// user with a valid group
groupID, err := group.AddUserGroup(models.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 group.DeleteUserGroup(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')`,

View File

@ -1,61 +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 dao
import (
"github.com/astaxie/beego/orm"
"github.com/goharbor/harbor/src/common/models"
)
// GetUserProjectRoles returns roles that the user has according to the project.
func GetUserProjectRoles(userID int, projectID int64, entityType string) ([]models.Role, error) {
o := GetOrmer()
sql := `select *
from role
where role_id =
(
select role
from project_member
where project_id = ? and entity_id = ? and entity_type = 'u'
)`
var roleList []models.Role
_, err := o.Raw(sql, projectID, userID).QueryRows(&roleList)
if err != nil {
return nil, err
}
return roleList, nil
}
// GetRoleByID ...
func GetRoleByID(id int) (*models.Role, error) {
o := GetOrmer()
sql := `select *
from role
where role_id = ?`
var role models.Role
if err := o.Raw(sql, id).QueryRow(&role); err != nil {
if err == orm.ErrNoRows {
return nil, nil
}
return nil, err
}
return &role, nil
}

View File

@ -14,20 +14,10 @@
package models
const (
// PROJECTADMIN project administrator
PROJECTADMIN = 1
// DEVELOPER developer
DEVELOPER = 2
// GUEST guest
GUEST = 3
)
// Role holds the details of a role.
type Role struct {
RoleID int `orm:"pk;auto;column(role_id)" json:"role_id"`
RoleCode string `orm:"column(role_code)" json:"role_code"`
Name string `orm:"column(name)" json:"role_name"`
RoleMask int `orm:"column(role_mask)" json:"role_mask"`
RoleMask int `orm:"column(role_mask)" json:"role_mask"`
}

View File

@ -15,6 +15,8 @@
package rbac
import (
pro "github.com/goharbor/harbor/src/common/dao/project"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/core/promgr"
"github.com/goharbor/harbor/src/lib/log"
@ -24,8 +26,8 @@ import (
"github.com/goharbor/harbor/src/pkg/permission/types"
)
// NewProjectRBACEvaluator returns permission evaluator for project
func NewProjectRBACEvaluator(ctx security.Context, pm promgr.ProjectManager) evaluator.Evaluator {
// NewProjectUserEvaluator returns permission evaluator for project
func NewProjectUserEvaluator(user *models.User, pm promgr.ProjectManager) evaluator.Evaluator {
return namespace.New(ProjectNamespaceKind, func(ns types.Namespace) evaluator.Evaluator {
project, err := pm.Get(ns.Identity())
if err != nil || project == nil {
@ -35,9 +37,13 @@ func NewProjectRBACEvaluator(ctx security.Context, pm promgr.ProjectManager) eva
return nil
}
if ctx.IsAuthenticated() {
roles := ctx.GetProjectRoles(project.ProjectID)
return rbac.New(NewProjectRBACUser(project, ctx.GetUsername(), roles...))
if user != nil {
roles, err := pro.ListRoles(user, project.ProjectID)
if err != nil {
log.Errorf("failed to list roles: %v", err)
return nil
}
return rbac.New(NewProjectRBACUser(project, user.Username, roles...))
} else if project.IsPublic() {
// anonymous access and the project is public
return rbac.New(NewProjectRBACUser(project, "anonymous"))

View File

@ -15,15 +15,17 @@
package rbac
import (
"testing"
"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"
promgr "github.com/goharbor/harbor/src/core/promgr/mocks"
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/testing/common/security"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
"testing"
)
var (
@ -72,10 +74,10 @@ func makeResource(subresource ...types.Resource) types.Resource {
func TestAnonymousAccess(t *testing.T) {
assert := assert.New(t)
evaluator1 := NewProjectRBACEvaluator(anonymousSecurity, publicProjectManager)
evaluator1 := NewProjectUserEvaluator(nil, publicProjectManager)
assert.True(evaluator1.HasPermission(makeResource(ResourceRepository), ActionPull))
evaluator2 := NewProjectRBACEvaluator(anonymousSecurity, privateProjectManager)
evaluator2 := NewProjectUserEvaluator(nil, privateProjectManager)
assert.False(evaluator2.HasPermission(makeResource(ResourceRepository), ActionPull))
evaluator3 := NewProjectRobotEvaluator(anonymousSecurity, publicProjectManager, func(ns types.Namespace) types.RBACUser { return nil })
@ -87,15 +89,41 @@ func TestAnonymousAccess(t *testing.T) {
func TestProjectRoleAccess(t *testing.T) {
assert := assert.New(t)
evaluator1 := NewProjectRBACEvaluator(projectAdminSecurity, publicProjectManager)
assert.True(evaluator1.HasPermission(makeResource(ResourceRepository), ActionPush))
dao.PrepareTestForPostgresSQL()
evaluator2 := NewProjectRBACEvaluator(guestSecurity, publicProjectManager)
assert.False(evaluator2.HasPermission(makeResource(ResourceRepository), ActionPush))
projectID, err := dao.AddProject(models.Project{
OwnerID: 1,
Name: "project_for_test_evaluator",
})
require.Nil(t, err)
defer dao.DeleteProject(projectID)
pm := makeMockProjectManager(projectID, true)
memberID, err := project.AddProjectMember(models.Member{
ProjectID: projectID,
Role: common.RoleProjectAdmin,
EntityID: 1,
EntityType: "u",
})
require.Nil(t, err)
defer project.DeleteProjectMemberByID(memberID)
evaluator1 := NewProjectUserEvaluator(&models.User{
UserID: 1,
Username: "admin",
}, pm)
assert.True(evaluator1.HasPermission(NewProjectNamespace(projectID).Resource(ResourceRepository), ActionPush))
project.UpdateProjectMemberRole(memberID, common.RoleGuest)
evaluator2 := NewProjectUserEvaluator(&models.User{
UserID: 1,
Username: "admin",
}, pm)
assert.False(evaluator2.HasPermission(NewProjectNamespace(projectID).Resource(ResourceRepository), ActionPush))
}
func BenchmarkProjectRBACEvaluator(b *testing.B) {
evaluator := NewProjectRBACEvaluator(projectAdminSecurity, publicProjectManager)
evaluator := NewProjectUserEvaluator(nil, publicProjectManager)
resource := NewProjectNamespace(projectID).Resource(ResourceRepository)
b.ResetTimer()
@ -105,7 +133,7 @@ func BenchmarkProjectRBACEvaluator(b *testing.B) {
}
func BenchmarkProjectRBACEvaluatorParallel(b *testing.B) {
evaluator := NewProjectRBACEvaluator(projectAdminSecurity, publicProjectManager)
evaluator := NewProjectUserEvaluator(nil, publicProjectManager)
resource := NewProjectNamespace(projectID).Resource(ResourceRepository)
b.RunParallel(func(pb *testing.PB) {

View File

@ -17,7 +17,6 @@ package security
import (
"context"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/pkg/permission/types"
)
@ -33,10 +32,6 @@ type Context interface {
IsSysAdmin() bool
// IsSolutionUser returns whether the user is solution user
IsSolutionUser() bool
// Get current user's all project
GetMyProjects() ([]*models.Project, error)
// Get user's role in provided project
GetProjectRoles(projectIDOrName interface{}) []int
// Can returns whether the user can do action on resource
Can(action types.Action, resource types.Resource) bool
}

View File

@ -17,12 +17,9 @@ package local
import (
"sync"
"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/common/rbac"
"github.com/goharbor/harbor/src/core/promgr"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
"github.com/goharbor/harbor/src/pkg/permission/evaluator/admin"
"github.com/goharbor/harbor/src/pkg/permission/types"
@ -89,109 +86,10 @@ func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool
if s.IsSysAdmin() {
evaluators = evaluators.Add(admin.New(s.GetUsername()))
}
evaluators = evaluators.Add(rbac.NewProjectRBACEvaluator(s, s.pm))
evaluators = evaluators.Add(rbac.NewProjectUserEvaluator(s.User(), s.pm))
s.evaluator = evaluators
})
return s.evaluator != nil && s.evaluator.HasPermission(resource, action)
}
// GetProjectRoles ...
func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
if !s.IsAuthenticated() || projectIDOrName == nil {
return []int{}
}
roles := []int{}
user, err := dao.GetUser(models.User{
Username: s.GetUsername(),
})
if err != nil {
log.Errorf("failed to get user %s: %v", s.GetUsername(), err)
return roles
}
if user == nil {
log.Debugf("user %s not found", s.GetUsername())
return roles
}
project, err := s.pm.Get(projectIDOrName)
if err != nil {
log.Errorf("failed to get project %v: %v", projectIDOrName, err)
return roles
}
if project == nil {
log.Errorf("project %v not found", projectIDOrName)
return roles
}
roleList, err := dao.GetUserProjectRoles(user.UserID, project.ProjectID, common.UserMember)
if err != nil {
log.Errorf("failed to get roles of user %d to project %d: %v", user.UserID, project.ProjectID, err)
return roles
}
for _, role := range roleList {
switch role.RoleCode {
case "MDRWS":
roles = append(roles, common.RoleProjectAdmin)
case "DRWS":
roles = append(roles, common.RoleMaster)
case "RWS":
roles = append(roles, common.RoleDeveloper)
case "RS":
roles = append(roles, common.RoleGuest)
case "LRS":
roles = append(roles, common.RoleLimitedGuest)
}
}
return mergeRoles(roles, s.GetRolesByGroup(projectIDOrName))
}
func mergeRoles(rolesA, rolesB []int) []int {
type void struct{}
var roles []int
var placeHolder void
roleSet := make(map[int]void)
for _, r := range rolesA {
roleSet[r] = placeHolder
}
for _, r := range rolesB {
roleSet[r] = placeHolder
}
for r := range roleSet {
roles = append(roles, r)
}
return roles
}
// GetRolesByGroup - Get the group role of current user to the project
func (s *SecurityContext) GetRolesByGroup(projectIDOrName interface{}) []int {
var roles []int
user := s.user
project, err := s.pm.Get(projectIDOrName)
// No user, group or project info
if err != nil || project == nil || user == nil || len(user.GroupIDs) == 0 {
return roles
}
// Get role by Group ID
roles, err = dao.GetRolesByGroupID(project.ProjectID, user.GroupIDs)
if err != nil {
return nil
}
return roles
}
// GetMyProjects ...
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
result, err := s.pm.List(
&models.ProjectQueryParam{
Member: &models.MemberQuery{
Name: s.GetUsername(),
GroupIDs: s.user.GroupIDs,
},
})
if err != nil {
return nil, err
}
return result.Projects, nil
}

View File

@ -29,7 +29,6 @@ import (
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
"github.com/goharbor/harbor/src/lib/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@ -272,43 +271,6 @@ func TestHasPushPullPermWithGroup(t *testing.T) {
assert.True(t, ctx.Can(rbac.ActionPull, resource))
}
func TestGetMyProjects(t *testing.T) {
ctx := NewSecurityContext(guestUser, pm)
projects, err := ctx.GetMyProjects()
require.Nil(t, err)
assert.Equal(t, 1, len(projects))
assert.Equal(t, private.ProjectID, projects[0].ProjectID)
}
func TestGetProjectRoles(t *testing.T) {
// unauthenticated
ctx := NewSecurityContext(nil, pm)
roles := ctx.GetProjectRoles(private.Name)
assert.Equal(t, 0, len(roles))
// authenticated, project name of ID is nil
ctx = NewSecurityContext(guestUser, pm)
roles = ctx.GetProjectRoles(nil)
assert.Equal(t, 0, len(roles))
// authenticated, has read perm
ctx = NewSecurityContext(guestUser, pm)
roles = ctx.GetProjectRoles(private.Name)
assert.Equal(t, 1, len(roles))
assert.Equal(t, common.RoleGuest, roles[0])
// authenticated, has write perm
ctx = NewSecurityContext(developerUser, pm)
roles = ctx.GetProjectRoles(private.Name)
assert.Equal(t, 1, len(roles))
assert.Equal(t, common.RoleDeveloper, roles[0])
// authenticated, has all perms
ctx = NewSecurityContext(projectAdminUser, pm)
roles = ctx.GetProjectRoles(private.Name)
assert.Equal(t, 1, len(roles))
assert.Equal(t, common.RoleProjectAdmin, roles[0])
}
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')`,
@ -329,106 +291,3 @@ func PrepareGroupTest() {
}
dao.PrepareTestData(clearSqls, initSqls)
}
func TestSecurityContext_GetRolesByGroup(t *testing.T) {
PrepareGroupTest()
project, err := dao.GetProjectByName("group_project")
if err != nil {
t.Errorf("Error occurred when GetProjectByName: %v", err)
}
developer, err := dao.GetUser(models.User{Username: "sample01"})
if err != nil {
t.Errorf("Error occurred when GetUser: %v", err)
}
userGroups, err := group.QueryUserGroup(models.UserGroup{GroupType: common.LDAPGroupType, LdapGroupDN: "cn=harbor_user,dc=example,dc=com"})
if err != nil {
t.Errorf("Failed to query user group %v", err)
}
if len(userGroups) < 1 {
t.Errorf("Failed to retrieve user group")
}
developer.GroupIDs = []int{userGroups[0].ID}
type fields struct {
user *models.User
pm promgr.ProjectManager
}
type args struct {
projectIDOrName interface{}
}
tests := []struct {
name string
fields fields
args args
want []int
}{
{"Developer", fields{user: developer, pm: pm}, args{project.ProjectID}, []int{2}},
{"Guest", fields{user: guestUser, pm: pm}, args{project.ProjectID}, []int{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &SecurityContext{
user: tt.fields.user,
pm: tt.fields.pm,
}
if got := s.GetRolesByGroup(tt.args.projectIDOrName); !dao.ArrayEqual(got, tt.want) {
t.Errorf("SecurityContext.GetRolesByGroup() = %v, want %v", got, tt.want)
}
})
}
}
func TestSecurityContext_GetMyProjects(t *testing.T) {
type fields struct {
user *models.User
pm promgr.ProjectManager
}
tests := []struct {
name string
fields fields
wantSize int
wantErr bool
}{
{"Admin", fields{user: projectAdminUser, pm: pm}, 1, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s := &SecurityContext{
user: tt.fields.user,
pm: tt.fields.pm,
}
got, err := s.GetMyProjects()
if (err != nil) != tt.wantErr {
t.Errorf("SecurityContext.GetMyProjects() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got) != tt.wantSize {
t.Errorf("SecurityContext.GetMyProjects() = %v, want %v", len(got), tt.wantSize)
}
})
}
}
func Test_mergeRoles(t *testing.T) {
type args struct {
rolesA []int
rolesB []int
}
tests := []struct {
name string
args args
want []int
}{
{"normal", args{[]int{3, 4}, []int{1, 2, 3, 4}}, []int{1, 2, 3, 4}},
{"empty", args{[]int{}, []int{}}, []int{}},
{"left empty", args{[]int{}, []int{1, 2, 3, 4}}, []int{1, 2, 3, 4}},
{"right empty", args{[]int{1, 2, 3, 4}, []int{}}, []int{1, 2, 3, 4}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := mergeRoles(tt.args.rolesA, tt.args.rolesB); !test.CheckSetsEqual(got, tt.want) {
t.Errorf("mergeRoles() = %v, want %v", got, tt.want)
}
})
}
}

View File

@ -17,7 +17,6 @@ package robot
import (
"sync"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/core/promgr"
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
@ -72,16 +71,6 @@ func (s *SecurityContext) IsSolutionUser() bool {
return false
}
// GetMyProjects no implementation
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
return nil, nil
}
// GetProjectRoles no implementation
func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
return nil
}
// Can returns whether the robot can do action on resource
func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool {
s.once.Do(func() {

View File

@ -29,7 +29,6 @@ import (
"github.com/goharbor/harbor/src/pkg/permission/types"
"github.com/goharbor/harbor/src/pkg/robot/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@ -152,16 +151,3 @@ func TestHasPushPullPerm(t *testing.T) {
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
assert.True(t, ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource))
}
func TestGetMyProjects(t *testing.T) {
ctx := NewSecurityContext(nil, nil, nil)
projects, err := ctx.GetMyProjects()
require.Nil(t, err)
assert.Nil(t, projects)
}
func TestGetProjectRoles(t *testing.T) {
ctx := NewSecurityContext(nil, nil, nil)
roles := ctx.GetProjectRoles("test")
assert.Nil(t, roles)
}

View File

@ -15,10 +15,6 @@
package secret
import (
"fmt"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/secret"
"github.com/goharbor/harbor/src/lib/log"
"github.com/goharbor/harbor/src/pkg/permission/types"
@ -85,19 +81,3 @@ func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool
}
return s.store.GetUsername(s.secret) == secret.JobserviceUser || s.store.GetUsername(s.secret) == secret.CoreUser
}
// GetMyProjects ...
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
return nil, fmt.Errorf("GetMyProjects is unsupported")
}
// GetProjectRoles return guest role if has read permission, otherwise return nil
func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
roles := []int{}
if s.store != nil &&
(s.store.GetUsername(s.secret) == secret.JobserviceUser ||
s.store.GetUsername(s.secret) == secret.CoreUser) {
roles = append(roles, common.RoleGuest)
}
return roles
}

View File

@ -17,7 +17,6 @@ package secret
import (
"testing"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/secret"
"github.com/stretchr/testify/assert"
@ -154,34 +153,3 @@ func TestHasPushPullPerm(t *testing.T) {
resource = rbac.Resource("/project/1/repository")
assert.False(t, context.Can(rbac.ActionPush, resource) && context.Can(rbac.ActionPull, resource))
}
func TestGetMyProjects(t *testing.T) {
context := NewSecurityContext("secret",
secret.NewStore(map[string]string{
"secret": "username",
}))
_, err := context.GetMyProjects()
assert.NotNil(t, err)
}
func TestGetProjectRoles(t *testing.T) {
// invalid secret
context := NewSecurityContext("invalid_secret",
secret.NewStore(map[string]string{
"jobservice_secret": secret.JobserviceUser,
}))
roles := context.GetProjectRoles("any_project")
assert.Equal(t, 0, len(roles))
// valid secret
context = NewSecurityContext("jobservice_secret",
secret.NewStore(map[string]string{
"jobservice_secret": secret.JobserviceUser,
}))
roles = context.GetProjectRoles("any_project")
assert.Equal(t, 1, len(roles))
assert.Equal(t, common.RoleGuest, roles[0])
}

View File

@ -212,6 +212,10 @@ func (f *fakedProjectMgr) GetPublic() ([]*models.Project, error) {
return nil, nil
}
func (f *fakedProjectMgr) GetAuthorized(user *models.User) ([]*models.Project, error) {
return nil, nil
}
// if the project manager uses a metadata manager, return it, otherwise return nil
func (f *fakedProjectMgr) GetMetadataManager() metamgr.ProjectMetadataManager {
return nil

View File

@ -252,7 +252,7 @@ func prepare() error {
if projAdminPMID, err = project.AddProjectMember(models.Member{
ProjectID: 1,
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
EntityID: int(projAdminID),
EntityType: common.UserMember,
}); err != nil {
@ -271,7 +271,7 @@ func prepare() error {
if projAdminRobotPMID, err = project.AddProjectMember(models.Member{
ProjectID: 1,
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
EntityID: int(projAdminRobotID),
EntityType: common.UserMember,
}); err != nil {
@ -290,7 +290,7 @@ func prepare() error {
if projDeveloperPMID, err = project.AddProjectMember(models.Member{
ProjectID: 1,
Role: models.DEVELOPER,
Role: common.RoleDeveloper,
EntityID: int(projDeveloperID),
EntityType: common.UserMember,
}); err != nil {
@ -309,7 +309,7 @@ func prepare() error {
if projGuestPMID, err = project.AddProjectMember(models.Member{
ProjectID: 1,
Role: models.GUEST,
Role: common.RoleGuest,
EntityID: int(projGuestID),
EntityType: common.UserMember,
}); err != nil {

View File

@ -258,6 +258,10 @@ func (mpm *mockProjectManager) GetPublic() ([]*models.Project, error) {
return nil, errors.New("Not implemented")
}
func (mpm *mockProjectManager) GetAuthorized(user *models.User) ([]*models.Project, error) {
return nil, nil
}
// if the project manager uses a metadata manager, return it, otherwise return nil
func (mpm *mockProjectManager) GetMetadataManager() metamgr.ProjectMetadataManager {
return nil

View File

@ -24,10 +24,11 @@ import (
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/dao/project"
pro "github.com/goharbor/harbor/src/common/dao/project"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/quota"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/security/local"
"github.com/goharbor/harbor/src/common/utils"
errutil "github.com/goharbor/harbor/src/common/utils/error"
"github.com/goharbor/harbor/src/controller/event/metadata"
@ -402,12 +403,14 @@ func (p *ProjectAPI) List() {
return
}
projects = append(projects, pros...)
mps, err := p.SecurityCtx.GetMyProjects()
if err != nil {
p.SendInternalServerError(fmt.Errorf("failed to list projects: %v", err))
return
if sc, ok := p.SecurityCtx.(*local.SecurityContext); ok {
mps, err := p.ProjectMgr.GetAuthorized(sc.User())
if err != nil {
p.SendInternalServerError(fmt.Errorf("failed to list authorized projects: %v", err))
return
}
projects = append(projects, mps...)
}
projects = append(projects, mps...)
}
}
// Query projects by user group
@ -443,8 +446,11 @@ func (p *ProjectAPI) populateProperties(project *models.Project) error {
project.SetMetadata(models.ProMetaSeverity, strings.ToLower(vuln.ParseSeverityVersion3(severity).String()))
}
if p.SecurityCtx.IsAuthenticated() {
roles := p.SecurityCtx.GetProjectRoles(project.ProjectID)
if sc, ok := p.SecurityCtx.(*local.SecurityContext); ok {
roles, err := pro.ListRoles(sc.User(), project.ProjectID)
if err != nil {
return err
}
project.RoleList = roles
project.Role = highestRole(roles)
}
@ -614,7 +620,7 @@ func getProjectMemberSummary(projectID int64, summary *models.ProjectSummary) {
go func(role int, count *int64) {
defer wg.Done()
total, err := project.GetTotalOfProjectMembers(projectID, role)
total, err := pro.GetTotalOfProjectMembers(projectID, role)
if err != nil {
log.Debugf("failed to get total of project members of role %d", role)
return

View File

@ -16,6 +16,8 @@ package api
import (
"fmt"
pro "github.com/goharbor/harbor/src/common/dao/project"
"github.com/goharbor/harbor/src/common/security/local"
"strings"
"github.com/goharbor/harbor/src/common/dao"
@ -47,7 +49,6 @@ type searchResult struct {
// Get ...
func (s *SearchAPI) Get() {
keyword := s.GetString("q")
isAuthenticated := s.SecurityCtx.IsAuthenticated()
isSysAdmin := s.SecurityCtx.IsSysAdmin()
var projects []*models.Project
@ -66,11 +67,11 @@ func (s *SearchAPI) Get() {
s.ParseAndHandleError("failed to get projects", err)
return
}
if isAuthenticated {
mys, err := s.SecurityCtx.GetMyProjects()
if sc, ok := s.SecurityCtx.(*local.SecurityContext); ok {
mys, err := s.ProjectMgr.GetAuthorized(sc.User())
if err != nil {
s.SendInternalServerError(fmt.Errorf(
"failed to get projects: %v", err))
"failed to get authorized projects: %v", err))
return
}
exist := map[int64]bool{}
@ -95,8 +96,12 @@ func (s *SearchAPI) Get() {
continue
}
if isAuthenticated {
roles := s.SecurityCtx.GetProjectRoles(p.ProjectID)
if sc, ok := s.SecurityCtx.(*local.SecurityContext); ok {
roles, err := pro.ListRoles(sc.User(), p.ProjectID)
if err != nil {
s.SendInternalServerError(fmt.Errorf("failed to list roles: %v", err))
return
}
p.Role = highestRole(roles)
}

View File

@ -63,7 +63,7 @@ func TestSearch(t *testing.T) {
ProjectID: projectID1,
EntityID: int(nonSysAdminID),
EntityType: common.UserMember,
Role: models.GUEST,
Role: common.RoleGuest,
})
require.Nil(t, err)
defer member.DeleteProjectMemberByID(memberID1)
@ -81,7 +81,7 @@ func TestSearch(t *testing.T) {
ProjectID: projectID2,
EntityID: int(nonSysAdminID),
EntityType: common.UserMember,
Role: models.GUEST,
Role: common.RoleGuest,
})
require.Nil(t, err)
defer member.DeleteProjectMemberByID(memberID2)

View File

@ -17,6 +17,7 @@ package api
import (
"errors"
"fmt"
"github.com/goharbor/harbor/src/common/security/local"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
@ -101,17 +102,18 @@ func (s *StatisticAPI) Get() {
statistic[TRC] = n
statistic[PriRC] = n - statistic[PubRC]
} else {
// including the public ones
myProjects, err := s.SecurityCtx.GetMyProjects()
privProjectIDs := make([]int64, 0)
if err != nil {
s.ParseAndHandleError(fmt.Sprintf(
"failed to get projects of user %s", s.username), err)
return
}
for _, p := range myProjects {
if !p.IsPublic() {
privProjectIDs = append(privProjectIDs, p.ProjectID)
if sc, ok := s.SecurityCtx.(*local.SecurityContext); ok {
myProjects, err := s.ProjectMgr.GetAuthorized(sc.User())
if err != nil {
s.ParseAndHandleError(fmt.Sprintf(
"failed to get projects of user %s", s.username), err)
return
}
for _, p := range myProjects {
if !p.IsPublic() {
privProjectIDs = append(privProjectIDs, p.ProjectID)
}
}
}

View File

@ -368,7 +368,7 @@ func TestAddProjectMemberWithLdapUser(t *testing.T) {
MemberUser: models.User{
Username: "mike",
},
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
}
pmid, err := api.AddProjectMember(currentProject.ProjectID, member)
if err != nil {
@ -387,7 +387,7 @@ func TestAddProjectMemberWithLdapUser(t *testing.T) {
MemberUser: models.User{
Username: "mike",
},
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
}
pmid, err = api.AddProjectMember(currentProject.ProjectID, member2)
if err != nil {
@ -409,7 +409,7 @@ func TestAddProjectMemberWithLdapGroup(t *testing.T) {
MemberGroup: models.UserGroup{
ID: groupIds[0],
},
Role: models.PROJECTADMIN,
Role: common.RoleProjectAdmin,
}
pmid, err := api.AddProjectMember(currentProject.ProjectID, member)
if err != nil {

View File

@ -93,6 +93,29 @@ func (_m *ProjectManager) Get(projectIDOrName interface{}) (*models.Project, err
return r0, r1
}
// GetAuthorized provides a mock function with given fields: user
func (_m *ProjectManager) GetAuthorized(user *models.User) ([]*models.Project, error) {
ret := _m.Called(user)
var r0 []*models.Project
if rf, ok := ret.Get(0).(func(*models.User) []*models.Project); ok {
r0 = rf(user)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*models.Project)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(*models.User) error); ok {
r1 = rf(user)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetMetadataManager provides a mock function with given fields:
func (_m *ProjectManager) GetMetadataManager() metamgr.ProjectMetadataManager {
ret := _m.Called()

View File

@ -37,6 +37,8 @@ type ProjectManager interface {
Exists(projectIDOrName interface{}) (bool, error)
// get all public project
GetPublic() ([]*models.Project, error)
// get all projects that the user is authorized
GetAuthorized(user *models.User) ([]*models.Project, error)
// if the project manager uses a metadata manager, return it, otherwise return nil
GetMetadataManager() metamgr.ProjectMetadataManager
}
@ -253,6 +255,23 @@ func (d *defaultProjectManager) GetPublic() ([]*models.Project, error) {
return result.Projects, nil
}
func (d *defaultProjectManager) GetAuthorized(user *models.User) ([]*models.Project, error) {
if user == nil {
return nil, nil
}
result, err := d.List(
&models.ProjectQueryParam{
Member: &models.MemberQuery{
Name: user.Username,
GroupIDs: user.GroupIDs,
},
})
if err != nil {
return nil, err
}
return result.Projects, nil
}
func (d *defaultProjectManager) GetMetadataManager() metamgr.ProjectMetadataManager {
return d.metaMgr
}

View File

@ -120,6 +120,16 @@ func TestGetPublic(t *testing.T) {
assert.True(t, projects[0].IsPublic())
}
func TestGetAuthorized(t *testing.T) {
projects, err := proMgr.GetAuthorized(nil)
require.Nil(t, err)
assert.Len(t, projects, 0)
projects, err = proMgr.GetAuthorized(&models.User{UserID: 1})
require.Nil(t, err)
assert.Len(t, projects, 1)
}
func TestGetMetadataManager(t *testing.T) {
metaMgr := proMgr.GetMetadataManager()
assert.Nil(t, metaMgr)

View File

@ -66,8 +66,10 @@ func QuerySetter(ctx context.Context, model interface{}, query *q.Query, ignored
// or list
ol, ok := v.(*q.OrList)
if ok && len(ol.Values) > 0 {
qs = qs.Filter(k+"__in", ol.Values...)
if ok {
if len(ol.Values) > 0 {
qs = qs.Filter(k+"__in", ol.Values...)
}
continue
}

View File

@ -78,6 +78,10 @@ func (mockPM) GetPublic() ([]*models.Project, error) {
panic("implement me")
}
func (mockPM) GetAuthorized(user *models.User) ([]*models.Project, error) {
return nil, nil
}
func (mockPM) GetMetadataManager() metamgr.ProjectMetadataManager {
panic("implement me")
}

View File

@ -6,6 +6,8 @@ import (
"github.com/go-openapi/runtime/middleware"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/security/local"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/lib/q"
"github.com/goharbor/harbor/src/pkg/audit"
@ -39,17 +41,22 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud
}
if !secCtx.IsSysAdmin() {
// ToDo remove the dependency on GetMyProjects()
projects, err := secCtx.GetMyProjects()
if err != nil {
return a.SendError(ctx, fmt.Errorf(
"failed to get projects of user %s: %v", secCtx.GetUsername(), err))
}
ol := &q.OrList{}
for _, project := range projects {
if a.HasProjectPermission(ctx, project.ProjectID, rbac.ActionList, rbac.ResourceLog) {
ol.Values = append(ol.Values, project.ProjectID)
if sc, ok := secCtx.(*local.SecurityContext); ok {
projects, err := config.GlobalProjectMgr.GetAuthorized(sc.User())
if err != nil {
return a.SendError(ctx, fmt.Errorf(
"failed to get projects of user %s: %v", secCtx.GetUsername(), err))
}
for _, project := range projects {
if a.HasProjectPermission(ctx, project.ProjectID, rbac.ActionList, rbac.ResourceLog) {
ol.Values = append(ol.Values, project.ProjectID)
}
}
}
// make sure no project will be selected with the query
if len(ol.Values) == 0 {
ol.Values = append(ol.Values, -1)
}
query.Keywords["ProjectID"] = ol
}