mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-30 20:48:23 +01:00
Merge pull request #13306 from heww/refactor-security-context
refactor(security): use controller instead of promgr in security
This commit is contained in:
commit
535728d11f
@ -15,10 +15,10 @@
|
|||||||
package rbac
|
package rbac
|
||||||
|
|
||||||
import (
|
import (
|
||||||
pro "github.com/goharbor/harbor/src/common/dao/project"
|
"context"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator/namespace"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator/namespace"
|
||||||
@ -26,62 +26,81 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewProjectUserEvaluator returns permission evaluator for project
|
// ProjectRBACUserBuilder builder to make types.RBACUser for the project
|
||||||
func NewProjectUserEvaluator(user *models.User, pm promgr.ProjectManager) evaluator.Evaluator {
|
type ProjectRBACUserBuilder func(context.Context, *models.Project) types.RBACUser
|
||||||
return namespace.New(ProjectNamespaceKind, func(ns types.Namespace) evaluator.Evaluator {
|
|
||||||
project, err := pm.Get(ns.Identity())
|
// NewBuilderForUser create a builder for the local user
|
||||||
if err != nil || project == nil {
|
func NewBuilderForUser(user *models.User, ctl project.Controller) ProjectRBACUserBuilder {
|
||||||
if err != nil {
|
return func(ctx context.Context, p *models.Project) types.RBACUser {
|
||||||
log.Warningf("Failed to get info of project %d for permission evaluator, error: %v", ns.Identity(), err)
|
if user == nil {
|
||||||
|
// anonymous access
|
||||||
|
return &projectRBACUser{
|
||||||
|
project: p,
|
||||||
|
username: "anonymous",
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if user != nil {
|
roles, err := ctl.ListRoles(ctx, p.ProjectID, user)
|
||||||
roles, err := pro.ListRoles(user, project.ProjectID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to list roles: %v", err)
|
log.Errorf("failed to list roles: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return rbac.New(NewProjectRBACUser(project, user.Username, roles...))
|
|
||||||
} else if project.IsPublic() {
|
return &projectRBACUser{
|
||||||
// anonymous access and the project is public
|
project: p,
|
||||||
return rbac.New(NewProjectRBACUser(project, "anonymous"))
|
username: user.Username,
|
||||||
} else {
|
projectRoles: roles,
|
||||||
return nil
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProjectRobotEvaluator returns robot permission evaluator for project
|
// NewBuilderForPolicies create a builder for the policies
|
||||||
func NewProjectRobotEvaluator(ctx security.Context, pm promgr.ProjectManager,
|
func NewBuilderForPolicies(username string, policies []*types.Policy,
|
||||||
robotFactory func(types.Namespace) types.RBACUser) evaluator.Evaluator {
|
filters ...func(*models.Project, []*types.Policy) []*types.Policy) ProjectRBACUserBuilder {
|
||||||
|
|
||||||
return namespace.New(ProjectNamespaceKind, func(ns types.Namespace) evaluator.Evaluator {
|
return func(ctx context.Context, p *models.Project) types.RBACUser {
|
||||||
project, err := pm.Get(ns.Identity())
|
for _, filter := range filters {
|
||||||
if err != nil || project == nil {
|
policies = filter(p, policies)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &projectRBACUser{
|
||||||
|
project: p,
|
||||||
|
username: username,
|
||||||
|
policies: policies,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewProjectEvaluator create evaluator for the project by builders
|
||||||
|
func NewProjectEvaluator(ctl project.Controller, builders ...ProjectRBACUserBuilder) evaluator.Evaluator {
|
||||||
|
return namespace.New(ProjectNamespaceKind, func(ctx context.Context, ns types.Namespace) evaluator.Evaluator {
|
||||||
|
p, err := ctl.Get(ctx, ns.Identity().(int64), project.Metadata(true))
|
||||||
|
if err != nil {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warningf("Failed to get info of project %d for permission evaluator, error: %v", ns.Identity(), err)
|
log.Warningf("Failed to get info of project %d for permission evaluator, error: %v", ns.Identity(), err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.IsAuthenticated() {
|
var rbacUsers []types.RBACUser
|
||||||
evaluators := evaluator.Evaluators{
|
for _, builder := range builders {
|
||||||
rbac.New(robotFactory(ns)), // robot account access
|
if rbacUser := builder(ctx, p); rbacUser != nil {
|
||||||
|
rbacUsers = append(rbacUsers, rbacUser)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if project.IsPublic() {
|
switch len(rbacUsers) {
|
||||||
// authenticated access and the project is public
|
case 0:
|
||||||
evaluators = evaluators.Add(rbac.New(NewProjectRBACUser(project, ctx.GetUsername())))
|
return nil
|
||||||
|
case 1:
|
||||||
|
return rbac.New(rbacUsers[0])
|
||||||
|
default:
|
||||||
|
var evaluators evaluator.Evaluators
|
||||||
|
for _, rbacUser := range rbacUsers {
|
||||||
|
evaluators = evaluators.Add(rbac.New(rbacUser))
|
||||||
}
|
}
|
||||||
|
|
||||||
return evaluators
|
return evaluators
|
||||||
} else if project.IsPublic() {
|
|
||||||
// anonymous access and the project is public
|
|
||||||
return rbac.New(NewProjectRBACUser(project, "anonymous"))
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -15,130 +15,127 @@
|
|||||||
package rbac
|
package rbac
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/goharbor/harbor/src/common"
|
"context"
|
||||||
"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"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common"
|
||||||
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
|
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||||
|
"github.com/goharbor/harbor/src/testing/mock"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
projectID = int64(1)
|
public = &models.Project{
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: "public_project",
|
||||||
|
OwnerID: 1,
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"public": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
projectAdminSecurity = makeMockSecurity("projectAdmin", common.RoleProjectAdmin)
|
private = &models.Project{
|
||||||
guestSecurity = makeMockSecurity("guest", common.RoleGuest)
|
ProjectID: 2,
|
||||||
anonymousSecurity = makeMockSecurity("")
|
Name: "private_project",
|
||||||
|
OwnerID: 1,
|
||||||
publicProjectManager = makeMockProjectManager(projectID, true)
|
Metadata: map[string]string{
|
||||||
privateProjectManager = makeMockProjectManager(projectID, false)
|
"public": "false",
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeMockSecurity(username string, roles ...int) *security.Context {
|
|
||||||
var isAuthenticated bool
|
|
||||||
if username != "" {
|
|
||||||
isAuthenticated = true
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := &security.Context{}
|
|
||||||
ctx.On("IsAuthenticated").Return(isAuthenticated)
|
|
||||||
ctx.On("GetUsername").Return(username)
|
|
||||||
ctx.On("GetProjectRoles", mock.AnythingOfType("int64")).Return(roles)
|
|
||||||
|
|
||||||
return ctx
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeMockProjectManager(projectID int64, isPublic bool) *promgr.ProjectManager {
|
|
||||||
pm := &promgr.ProjectManager{}
|
|
||||||
|
|
||||||
project := &models.Project{ProjectID: projectID}
|
|
||||||
if isPublic {
|
|
||||||
project.SetMetadata(models.ProMetaPublic, "true")
|
|
||||||
} else {
|
|
||||||
project.SetMetadata(models.ProMetaPublic, "false")
|
|
||||||
}
|
|
||||||
|
|
||||||
pm.On("Get", projectID).Return(project, nil)
|
|
||||||
|
|
||||||
return pm
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeResource(subresource ...types.Resource) types.Resource {
|
|
||||||
return NewProjectNamespace(projectID).Resource(subresource...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAnonymousAccess(t *testing.T) {
|
func TestAnonymousAccess(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
evaluator1 := NewProjectUserEvaluator(nil, publicProjectManager)
|
|
||||||
assert.True(evaluator1.HasPermission(makeResource(ResourceRepository), ActionPull))
|
|
||||||
|
|
||||||
evaluator2 := NewProjectUserEvaluator(nil, privateProjectManager)
|
{
|
||||||
assert.False(evaluator2.HasPermission(makeResource(ResourceRepository), ActionPull))
|
// anonymous to access public project
|
||||||
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(public, nil)
|
||||||
|
|
||||||
evaluator3 := NewProjectRobotEvaluator(anonymousSecurity, publicProjectManager, func(ns types.Namespace) types.RBACUser { return nil })
|
resource := NewProjectNamespace(public.ProjectID).Resource(ResourceRepository)
|
||||||
assert.True(evaluator3.HasPermission(makeResource(ResourceRepository), ActionPull))
|
|
||||||
|
|
||||||
evaluator4 := NewProjectRobotEvaluator(anonymousSecurity, privateProjectManager, func(ns types.Namespace) types.RBACUser { return nil })
|
evaluator := NewProjectEvaluator(ctl, NewBuilderForUser(nil, ctl))
|
||||||
assert.False(evaluator4.HasPermission(makeResource(ResourceRepository), ActionPull))
|
assert.True(evaluator.HasPermission(context.TODO(), resource, ActionPull))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// anonymous to access private project
|
||||||
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
resource := NewProjectNamespace(private.ProjectID).Resource(ResourceRepository)
|
||||||
|
|
||||||
|
evaluator := NewProjectEvaluator(ctl, NewBuilderForUser(nil, ctl))
|
||||||
|
assert.False(evaluator.HasPermission(context.TODO(), resource, ActionPull))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProjectRoleAccess(t *testing.T) {
|
func TestProjectRoleAccess(t *testing.T) {
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
dao.PrepareTestForPostgresSQL()
|
|
||||||
|
|
||||||
projectID, err := dao.AddProject(models.Project{
|
{
|
||||||
OwnerID: 1,
|
ctl := &projecttesting.Controller{}
|
||||||
Name: "project_for_test_evaluator",
|
mock.OnAnything(ctl, "Get").Return(public, nil)
|
||||||
})
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleProjectAdmin}, nil)
|
||||||
require.Nil(t, err)
|
|
||||||
defer dao.DeleteProject(projectID)
|
|
||||||
|
|
||||||
pm := makeMockProjectManager(projectID, true)
|
user := &models.User{
|
||||||
|
|
||||||
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,
|
UserID: 1,
|
||||||
Username: "admin",
|
Username: "username",
|
||||||
}, pm)
|
}
|
||||||
assert.True(evaluator1.HasPermission(NewProjectNamespace(projectID).Resource(ResourceRepository), ActionPush))
|
evaluator := NewProjectEvaluator(ctl, NewBuilderForUser(user, ctl))
|
||||||
|
resorce := NewProjectNamespace(public.ProjectID).Resource(ResourceRepository)
|
||||||
|
assert.True(evaluator.HasPermission(context.TODO(), resorce, ActionPush))
|
||||||
|
}
|
||||||
|
|
||||||
project.UpdateProjectMemberRole(memberID, common.RoleGuest)
|
{
|
||||||
evaluator2 := NewProjectUserEvaluator(&models.User{
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(public, nil)
|
||||||
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleGuest}, nil)
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
UserID: 1,
|
UserID: 1,
|
||||||
Username: "admin",
|
Username: "username",
|
||||||
}, pm)
|
}
|
||||||
assert.False(evaluator2.HasPermission(NewProjectNamespace(projectID).Resource(ResourceRepository), ActionPush))
|
evaluator := NewProjectEvaluator(ctl, NewBuilderForUser(user, ctl))
|
||||||
|
resorce := NewProjectNamespace(public.ProjectID).Resource(ResourceRepository)
|
||||||
|
assert.False(evaluator.HasPermission(context.TODO(), resorce, ActionPush))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkProjectRBACEvaluator(b *testing.B) {
|
func BenchmarkProjectEvaluator(b *testing.B) {
|
||||||
evaluator := NewProjectUserEvaluator(nil, publicProjectManager)
|
ctl := &projecttesting.Controller{}
|
||||||
resource := NewProjectNamespace(projectID).Resource(ResourceRepository)
|
mock.OnAnything(ctl, "Get").Return(public, nil)
|
||||||
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleProjectAdmin}, nil)
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
UserID: 1,
|
||||||
|
Username: "username",
|
||||||
|
}
|
||||||
|
evaluator := NewProjectEvaluator(ctl, NewBuilderForUser(user, ctl))
|
||||||
|
resource := NewProjectNamespace(public.ProjectID).Resource(ResourceRepository)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
evaluator.HasPermission(resource, ActionPull)
|
evaluator.HasPermission(context.TODO(), resource, ActionPull)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkProjectRBACEvaluatorParallel(b *testing.B) {
|
func BenchmarkProjectEvaluatorParallel(b *testing.B) {
|
||||||
evaluator := NewProjectUserEvaluator(nil, publicProjectManager)
|
ctl := &projecttesting.Controller{}
|
||||||
resource := NewProjectNamespace(projectID).Resource(ResourceRepository)
|
mock.OnAnything(ctl, "Get").Return(public, nil)
|
||||||
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleProjectAdmin}, nil)
|
||||||
|
|
||||||
|
user := &models.User{
|
||||||
|
UserID: 1,
|
||||||
|
Username: "username",
|
||||||
|
}
|
||||||
|
evaluator := NewProjectEvaluator(ctl, NewBuilderForUser(user, ctl))
|
||||||
|
resource := NewProjectNamespace(public.ProjectID).Resource(ResourceRepository)
|
||||||
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
evaluator.HasPermission(resource, ActionPull)
|
evaluator.HasPermission(context.TODO(), resource, ActionPull)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -23,37 +23,31 @@ type projectRBACUser struct {
|
|||||||
project *models.Project
|
project *models.Project
|
||||||
username string
|
username string
|
||||||
projectRoles []int
|
projectRoles []int
|
||||||
|
policies []*types.Policy
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserName returns username of the visitor
|
// GetUserName returns username of the visitor
|
||||||
func (user *projectRBACUser) GetUserName() string {
|
func (pru *projectRBACUser) GetUserName() string {
|
||||||
return user.username
|
return pru.username
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPolicies returns policies of the visitor
|
// GetPolicies returns policies of the visitor
|
||||||
func (user *projectRBACUser) GetPolicies() []*types.Policy {
|
func (pru *projectRBACUser) GetPolicies() []*types.Policy {
|
||||||
if user.project.IsPublic() {
|
policies := pru.policies
|
||||||
return getPoliciesForPublicProject(user.project.ProjectID)
|
|
||||||
|
if pru.project.IsPublic() {
|
||||||
|
policies = append(policies, getPoliciesForPublicProject(pru.project.ProjectID)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return policies
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRoles returns roles of the visitor
|
// GetRoles returns roles of the visitor
|
||||||
func (user *projectRBACUser) GetRoles() []types.RBACRole {
|
func (pru *projectRBACUser) GetRoles() []types.RBACRole {
|
||||||
roles := []types.RBACRole{}
|
roles := []types.RBACRole{}
|
||||||
for _, roleID := range user.projectRoles {
|
for _, roleID := range pru.projectRoles {
|
||||||
roles = append(roles, &projectRBACRole{projectID: user.project.ProjectID, roleID: roleID})
|
roles = append(roles, &projectRBACRole{projectID: pru.project.ProjectID, roleID: roleID})
|
||||||
}
|
}
|
||||||
|
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProjectRBACUser returns RBACUser for the project
|
|
||||||
func NewProjectRBACUser(project *models.Project, username string, projectRoles ...int) types.RBACUser {
|
|
||||||
return &projectRBACUser{
|
|
||||||
project: project,
|
|
||||||
username: username,
|
|
||||||
projectRoles: projectRoles,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -33,7 +33,7 @@ type Context interface {
|
|||||||
// IsSolutionUser returns whether the user is solution user
|
// IsSolutionUser returns whether the user is solution user
|
||||||
IsSolutionUser() bool
|
IsSolutionUser() bool
|
||||||
// Can returns whether the user can do action on resource
|
// Can returns whether the user can do action on resource
|
||||||
Can(action types.Action, resource types.Resource) bool
|
Can(ctx context.Context, action types.Action, resource types.Resource) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type securityKey struct{}
|
type securityKey struct{}
|
||||||
|
@ -15,11 +15,12 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator/admin"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator/admin"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
@ -28,16 +29,16 @@ import (
|
|||||||
// SecurityContext implements security.Context interface based on database
|
// SecurityContext implements security.Context interface based on database
|
||||||
type SecurityContext struct {
|
type SecurityContext struct {
|
||||||
user *models.User
|
user *models.User
|
||||||
pm promgr.ProjectManager
|
ctl project.Controller
|
||||||
evaluator evaluator.Evaluator
|
evaluator evaluator.Evaluator
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSecurityContext ...
|
// NewSecurityContext ...
|
||||||
func NewSecurityContext(user *models.User, pm promgr.ProjectManager) *SecurityContext {
|
func NewSecurityContext(user *models.User) *SecurityContext {
|
||||||
return &SecurityContext{
|
return &SecurityContext{
|
||||||
user: user,
|
user: user,
|
||||||
pm: pm,
|
ctl: project.Ctl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,16 +81,17 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Can returns whether the user can do action on resource
|
// Can returns whether the user can do action on resource
|
||||||
func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool {
|
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
s.once.Do(func() {
|
s.once.Do(func() {
|
||||||
var evaluators evaluator.Evaluators
|
var evaluators evaluator.Evaluators
|
||||||
if s.IsSysAdmin() {
|
if s.IsSysAdmin() {
|
||||||
evaluators = evaluators.Add(admin.New(s.GetUsername()))
|
evaluators = evaluators.Add(admin.New(s.GetUsername()))
|
||||||
}
|
}
|
||||||
evaluators = evaluators.Add(rbac.NewProjectUserEvaluator(s.User(), s.pm))
|
|
||||||
|
evaluators = evaluators.Add(rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForUser(s.user, s.ctl)))
|
||||||
|
|
||||||
s.evaluator = evaluators
|
s.evaluator = evaluators
|
||||||
})
|
})
|
||||||
|
|
||||||
return s.evaluator != nil && s.evaluator.HasPermission(resource, action)
|
return s.evaluator != nil && s.evaluator.HasPermission(ctx, resource, action)
|
||||||
}
|
}
|
||||||
|
@ -15,26 +15,35 @@
|
|||||||
package local
|
package local
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
"github.com/goharbor/harbor/src/common/dao"
|
|
||||||
"github.com/goharbor/harbor/src/common/dao/group"
|
|
||||||
"github.com/goharbor/harbor/src/common/dao/project"
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/utils/test"
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
|
"github.com/goharbor/harbor/src/testing/mock"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
public = &project.Project{
|
||||||
|
ProjectID: 1,
|
||||||
|
Name: "public_project",
|
||||||
|
OwnerID: 1,
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"public": "true",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
private = &models.Project{
|
private = &models.Project{
|
||||||
|
ProjectID: 2,
|
||||||
Name: "private_project",
|
Name: "private_project",
|
||||||
OwnerID: 1,
|
OwnerID: 1,
|
||||||
|
Metadata: map[string]string{
|
||||||
|
"public": "false",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
projectAdminUser = &models.User{
|
projectAdminUser = &models.User{
|
||||||
@ -49,245 +58,199 @@ var (
|
|||||||
Username: "guestUser",
|
Username: "guestUser",
|
||||||
Email: "guestUser@vmware.com",
|
Email: "guestUser@vmware.com",
|
||||||
}
|
}
|
||||||
|
|
||||||
pm = promgr.NewDefaultProjectManager(local.NewDriver(), true)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
|
|
||||||
test.InitDatabaseFromEnv()
|
|
||||||
|
|
||||||
// regiser users
|
|
||||||
id, err := dao.Register(*projectAdminUser)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to register user: %v", err)
|
|
||||||
}
|
|
||||||
projectAdminUser.UserID = int(id)
|
|
||||||
defer dao.DeleteUser(int(id))
|
|
||||||
|
|
||||||
id, err = dao.Register(*developerUser)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to register user: %v", err)
|
|
||||||
}
|
|
||||||
developerUser.UserID = int(id)
|
|
||||||
defer dao.DeleteUser(int(id))
|
|
||||||
|
|
||||||
id, err = dao.Register(*guestUser)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to register user: %v", err)
|
|
||||||
}
|
|
||||||
guestUser.UserID = int(id)
|
|
||||||
defer dao.DeleteUser(int(id))
|
|
||||||
|
|
||||||
// add project
|
|
||||||
id, err = dao.AddProject(*private)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to add project: %v", err)
|
|
||||||
}
|
|
||||||
private.ProjectID = id
|
|
||||||
defer dao.DeleteProject(id)
|
|
||||||
|
|
||||||
var projectAdminPMID, developerUserPMID, guestUserPMID int
|
|
||||||
// add project members
|
|
||||||
projectAdminPMID, err = project.AddProjectMember(models.Member{
|
|
||||||
ProjectID: private.ProjectID,
|
|
||||||
EntityID: projectAdminUser.UserID,
|
|
||||||
EntityType: common.UserMember,
|
|
||||||
Role: common.RoleProjectAdmin,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to add member: %v", err)
|
|
||||||
}
|
|
||||||
defer project.DeleteProjectMemberByID(projectAdminPMID)
|
|
||||||
|
|
||||||
developerUserPMID, err = project.AddProjectMember(models.Member{
|
|
||||||
ProjectID: private.ProjectID,
|
|
||||||
EntityID: developerUser.UserID,
|
|
||||||
EntityType: common.UserMember,
|
|
||||||
Role: common.RoleDeveloper,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to add member: %v", err)
|
|
||||||
}
|
|
||||||
defer project.DeleteProjectMemberByID(developerUserPMID)
|
|
||||||
guestUserPMID, err = project.AddProjectMember(models.Member{
|
|
||||||
ProjectID: private.ProjectID,
|
|
||||||
EntityID: guestUser.UserID,
|
|
||||||
EntityType: common.UserMember,
|
|
||||||
Role: common.RoleGuest,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to add member: %v", err)
|
|
||||||
}
|
|
||||||
defer project.DeleteProjectMemberByID(guestUserPMID)
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsAuthenticated(t *testing.T) {
|
func TestIsAuthenticated(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil)
|
||||||
assert.False(t, ctx.IsAuthenticated())
|
assert.False(t, ctx.IsAuthenticated())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctx = NewSecurityContext(&models.User{
|
||||||
Username: "test",
|
Username: "test",
|
||||||
}, nil)
|
})
|
||||||
assert.True(t, ctx.IsAuthenticated())
|
assert.True(t, ctx.IsAuthenticated())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUsername(t *testing.T) {
|
func TestGetUsername(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil)
|
||||||
assert.Equal(t, "", ctx.GetUsername())
|
assert.Equal(t, "", ctx.GetUsername())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctx = NewSecurityContext(&models.User{
|
||||||
Username: "test",
|
Username: "test",
|
||||||
}, nil)
|
})
|
||||||
assert.Equal(t, "test", ctx.GetUsername())
|
assert.Equal(t, "test", ctx.GetUsername())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsSysAdmin(t *testing.T) {
|
func TestIsSysAdmin(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil)
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
assert.False(t, ctx.IsSysAdmin())
|
||||||
|
|
||||||
// authenticated, non admin
|
// authenticated, non admin
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctx = NewSecurityContext(&models.User{
|
||||||
Username: "test",
|
Username: "test",
|
||||||
}, nil)
|
})
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
assert.False(t, ctx.IsSysAdmin())
|
||||||
|
|
||||||
// authenticated, admin
|
// authenticated, admin
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctx = NewSecurityContext(&models.User{
|
||||||
Username: "test",
|
Username: "test",
|
||||||
SysAdminFlag: true,
|
SysAdminFlag: true,
|
||||||
}, nil)
|
})
|
||||||
assert.True(t, ctx.IsSysAdmin())
|
assert.True(t, ctx.IsSysAdmin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsSolutionUser(t *testing.T) {
|
func TestIsSolutionUser(t *testing.T) {
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil)
|
||||||
assert.False(t, ctx.IsSolutionUser())
|
assert.False(t, ctx.IsSolutionUser())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasPullPerm(t *testing.T) {
|
func TestHasPullPerm(t *testing.T) {
|
||||||
|
{
|
||||||
// public project
|
// public project
|
||||||
ctx := NewSecurityContext(nil, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(public, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(nil)
|
||||||
|
ctx.ctl = ctl
|
||||||
resource := rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
||||||
assert.True(t, ctx.Can(rbac.ActionPull, resource))
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// private project, unauthenticated
|
// private project, unauthenticated
|
||||||
ctx = NewSecurityContext(nil, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
resource = rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
assert.False(t, ctx.Can(rbac.ActionPull, resource))
|
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(nil)
|
||||||
|
ctx.ctl = ctl
|
||||||
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
|
assert.False(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// private project, authenticated, has no perm
|
// private project, authenticated, has no perm
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctl := &projecttesting.Controller{}
|
||||||
Username: "test",
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
}, pm)
|
mock.OnAnything(ctl, "ListRoles").Return([]int{}, nil)
|
||||||
assert.False(t, ctx.Can(rbac.ActionPull, resource))
|
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(&models.User{Username: "test"})
|
||||||
|
ctx.ctl = ctl
|
||||||
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
|
assert.False(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// private project, authenticated, has read perm
|
// private project, authenticated, has read perm
|
||||||
ctx = NewSecurityContext(guestUser, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
assert.True(t, ctx.Can(rbac.ActionPull, resource))
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleGuest}, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(guestUser)
|
||||||
|
ctx.ctl = ctl
|
||||||
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// private project, authenticated, system admin
|
// private project, authenticated, system admin
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(&models.User{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
SysAdminFlag: true,
|
SysAdminFlag: true,
|
||||||
}, pm)
|
})
|
||||||
assert.True(t, ctx.Can(rbac.ActionPull, resource))
|
ctx.ctl = ctl
|
||||||
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasPushPerm(t *testing.T) {
|
func TestHasPushPerm(t *testing.T) {
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
|
|
||||||
|
{
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
assert.False(t, ctx.Can(rbac.ActionPush, resource))
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(nil)
|
||||||
|
ctx.ctl = ctl
|
||||||
|
assert.False(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// authenticated, has read perm
|
// authenticated, has read perm
|
||||||
ctx = NewSecurityContext(guestUser, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
assert.False(t, ctx.Can(rbac.ActionPush, resource))
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleGuest}, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(guestUser)
|
||||||
|
ctx.ctl = ctl
|
||||||
|
assert.False(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// authenticated, has write perm
|
// authenticated, has write perm
|
||||||
ctx = NewSecurityContext(developerUser, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
assert.True(t, ctx.Can(rbac.ActionPush, resource))
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleDeveloper}, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(developerUser)
|
||||||
|
ctx.ctl = ctl
|
||||||
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// authenticated, system admin
|
// authenticated, system admin
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctl := &projecttesting.Controller{}
|
||||||
|
ctx := NewSecurityContext(&models.User{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
SysAdminFlag: true,
|
SysAdminFlag: true,
|
||||||
}, pm)
|
})
|
||||||
assert.True(t, ctx.Can(rbac.ActionPush, resource))
|
ctx.ctl = ctl
|
||||||
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasPushPullPerm(t *testing.T) {
|
func TestHasPushPullPerm(t *testing.T) {
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
|
|
||||||
|
{
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
assert.False(t, ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource))
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(nil)
|
||||||
|
ctx.ctl = ctl
|
||||||
|
assert.False(t, ctx.Can(context.TODO(), rbac.ActionPush, resource) && ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// authenticated, has all perms
|
// authenticated, has all perms
|
||||||
ctx = NewSecurityContext(projectAdminUser, pm)
|
ctl := &projecttesting.Controller{}
|
||||||
assert.True(t, ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource))
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
mock.OnAnything(ctl, "ListRoles").Return([]int{common.RoleProjectAdmin}, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(projectAdminUser)
|
||||||
|
ctx.ctl = ctl
|
||||||
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource) && ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
// authenticated, system admin
|
// authenticated, system admin
|
||||||
ctx = NewSecurityContext(&models.User{
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(&models.User{
|
||||||
Username: "admin",
|
Username: "admin",
|
||||||
SysAdminFlag: true,
|
SysAdminFlag: true,
|
||||||
}, pm)
|
})
|
||||||
assert.True(t, ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource))
|
ctx.ctl = ctl
|
||||||
}
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource) && ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
func TestHasPushPullPermWithGroup(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}
|
|
||||||
|
|
||||||
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
|
|
||||||
|
|
||||||
ctx := NewSecurityContext(developer, pm)
|
|
||||||
assert.True(t, ctx.Can(rbac.ActionPush, resource))
|
|
||||||
assert.True(t, ctx.Can(rbac.ActionPull, resource))
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,11 @@ package proxycachesecret
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/project"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// const definition
|
// const definition
|
||||||
@ -34,16 +33,14 @@ const (
|
|||||||
// SecurityContext is the security context for proxy cache secret
|
// SecurityContext is the security context for proxy cache secret
|
||||||
type SecurityContext struct {
|
type SecurityContext struct {
|
||||||
repository string
|
repository string
|
||||||
getProject func(interface{}) (*models.Project, error)
|
ctl project.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSecurityContext returns an instance of the proxy cache secret security context
|
// NewSecurityContext returns an instance of the proxy cache secret security context
|
||||||
func NewSecurityContext(ctx context.Context, repository string) *SecurityContext {
|
func NewSecurityContext(repository string) *SecurityContext {
|
||||||
return &SecurityContext{
|
return &SecurityContext{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
getProject: func(i interface{}) (*models.Project, error) {
|
ctl: project.Ctl,
|
||||||
return project.Mgr.Get(ctx, i)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +70,7 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Can returns true only when requesting pull/push operation against the specific project
|
// Can returns true only when requesting pull/push operation against the specific project
|
||||||
func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool {
|
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
if !(action == rbac.ActionPull || action == rbac.ActionPush) {
|
if !(action == rbac.ActionPull || action == rbac.ActionPush) {
|
||||||
log.Debugf("unauthorized for action %s", action)
|
log.Debugf("unauthorized for action %s", action)
|
||||||
return false
|
return false
|
||||||
@ -83,18 +80,16 @@ func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool
|
|||||||
log.Debugf("got no namespace from the resource %s", resource)
|
log.Debugf("got no namespace from the resource %s", resource)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
project, err := s.getProject(namespace.Identity())
|
|
||||||
|
p, err := s.ctl.Get(ctx, namespace.Identity().(int64))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get project %v: %v", namespace.Identity(), err)
|
log.Errorf("failed to get project %v: %v", namespace.Identity(), err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if project == nil {
|
|
||||||
log.Debugf("project not found %v", namespace.Identity())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
pro, _ := utils.ParseRepository(s.repository)
|
pro, _ := utils.ParseRepository(s.repository)
|
||||||
if project.Name != pro {
|
if p.Name != pro {
|
||||||
log.Debugf("unauthorized for project %s", project.Name)
|
log.Debugf("unauthorized for project %s", p.Name)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -16,28 +16,27 @@ package proxycachesecret
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||||
"github.com/goharbor/harbor/src/testing/mock"
|
"github.com/goharbor/harbor/src/testing/mock"
|
||||||
"github.com/goharbor/harbor/src/testing/pkg/project"
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
)
|
)
|
||||||
|
|
||||||
type proxyCacheSecretTestSuite struct {
|
type proxyCacheSecretTestSuite struct {
|
||||||
suite.Suite
|
suite.Suite
|
||||||
sc *SecurityContext
|
sc *SecurityContext
|
||||||
mgr *project.Manager
|
ctl *projecttesting.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *proxyCacheSecretTestSuite) SetupTest() {
|
func (p *proxyCacheSecretTestSuite) SetupTest() {
|
||||||
p.mgr = &project.Manager{}
|
p.ctl = &projecttesting.Controller{}
|
||||||
p.sc = &SecurityContext{
|
p.sc = &SecurityContext{
|
||||||
repository: "library/hello-world",
|
repository: "library/hello-world",
|
||||||
getProject: func(i interface{}) (*models.Project, error) {
|
ctl: p.ctl,
|
||||||
return p.mgr.Get(context.TODO(), i)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,19 +64,19 @@ func (p *proxyCacheSecretTestSuite) TestCan() {
|
|||||||
// the action isn't pull/push
|
// the action isn't pull/push
|
||||||
action := rbac.ActionDelete
|
action := rbac.ActionDelete
|
||||||
resource := rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
||||||
p.False(p.sc.Can(action, resource))
|
p.False(p.sc.Can(context.TODO(), action, resource))
|
||||||
|
|
||||||
// the resource isn't repository
|
// the resource isn't repository
|
||||||
action = rbac.ActionPull
|
action = rbac.ActionPull
|
||||||
resource = rbac.ResourceConfiguration
|
resource = rbac.ResourceConfiguration
|
||||||
p.False(p.sc.Can(action, resource))
|
p.False(p.sc.Can(context.TODO(), action, resource))
|
||||||
|
|
||||||
// the requested project not found
|
// the requested project not found
|
||||||
action = rbac.ActionPull
|
action = rbac.ActionPull
|
||||||
resource = rbac.NewProjectNamespace(2).Resource(rbac.ResourceRepository)
|
resource = rbac.NewProjectNamespace(2).Resource(rbac.ResourceRepository)
|
||||||
p.mgr.On("Get", mock.Anything, mock.Anything).Return(nil, nil)
|
p.ctl.On("Get", mock.Anything, mock.Anything).Return(nil, errors.New("not found"))
|
||||||
p.False(p.sc.Can(action, resource))
|
p.False(p.sc.Can(context.TODO(), action, resource))
|
||||||
p.mgr.AssertExpectations(p.T())
|
p.ctl.AssertExpectations(p.T())
|
||||||
|
|
||||||
// reset the mock
|
// reset the mock
|
||||||
p.SetupTest()
|
p.SetupTest()
|
||||||
@ -85,12 +84,12 @@ func (p *proxyCacheSecretTestSuite) TestCan() {
|
|||||||
// pass for action pull
|
// pass for action pull
|
||||||
action = rbac.ActionPull
|
action = rbac.ActionPull
|
||||||
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
||||||
p.mgr.On("Get", mock.Anything, mock.Anything).Return(&models.Project{
|
p.ctl.On("Get", mock.Anything, mock.Anything).Return(&models.Project{
|
||||||
ProjectID: 1,
|
ProjectID: 1,
|
||||||
Name: "library",
|
Name: "library",
|
||||||
}, nil)
|
}, nil)
|
||||||
p.True(p.sc.Can(action, resource))
|
p.True(p.sc.Can(context.TODO(), action, resource))
|
||||||
p.mgr.AssertExpectations(p.T())
|
p.ctl.AssertExpectations(p.T())
|
||||||
|
|
||||||
// reset the mock
|
// reset the mock
|
||||||
p.SetupTest()
|
p.SetupTest()
|
||||||
@ -98,12 +97,12 @@ func (p *proxyCacheSecretTestSuite) TestCan() {
|
|||||||
// pass for action push
|
// pass for action push
|
||||||
action = rbac.ActionPush
|
action = rbac.ActionPush
|
||||||
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
resource = rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
|
||||||
p.mgr.On("Get", mock.Anything, mock.Anything).Return(&models.Project{
|
p.ctl.On("Get", mock.Anything, mock.Anything).Return(&models.Project{
|
||||||
ProjectID: 1,
|
ProjectID: 1,
|
||||||
Name: "library",
|
Name: "library",
|
||||||
}, nil)
|
}, nil)
|
||||||
p.True(p.sc.Can(action, resource))
|
p.True(p.sc.Can(context.TODO(), action, resource))
|
||||||
p.mgr.AssertExpectations(p.T())
|
p.ctl.AssertExpectations(p.T())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProxyCacheSecretTestSuite(t *testing.T) {
|
func TestProxyCacheSecretTestSuite(t *testing.T) {
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
package robot
|
package robot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
@ -27,17 +29,17 @@ import (
|
|||||||
// SecurityContext implements security.Context interface based on database
|
// SecurityContext implements security.Context interface based on database
|
||||||
type SecurityContext struct {
|
type SecurityContext struct {
|
||||||
robot *model.Robot
|
robot *model.Robot
|
||||||
pm promgr.ProjectManager
|
ctl project.Controller
|
||||||
policy []*types.Policy
|
policy []*types.Policy
|
||||||
evaluator evaluator.Evaluator
|
evaluator evaluator.Evaluator
|
||||||
once sync.Once
|
once sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSecurityContext ...
|
// NewSecurityContext ...
|
||||||
func NewSecurityContext(robot *model.Robot, pm promgr.ProjectManager, policy []*types.Policy) *SecurityContext {
|
func NewSecurityContext(robot *model.Robot, policy []*types.Policy) *SecurityContext {
|
||||||
return &SecurityContext{
|
return &SecurityContext{
|
||||||
|
ctl: project.Ctl,
|
||||||
robot: robot,
|
robot: robot,
|
||||||
pm: pm,
|
|
||||||
policy: policy,
|
policy: policy,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,14 +74,26 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Can returns whether the robot can do action on resource
|
// Can returns whether the robot can do action on resource
|
||||||
func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool {
|
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
s.once.Do(func() {
|
s.once.Do(func() {
|
||||||
robotFactory := func(ns types.Namespace) types.RBACUser {
|
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy, filterRobotPolicies))
|
||||||
return NewRobot(s.GetUsername(), ns, s.policy)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.evaluator = rbac.NewProjectRobotEvaluator(s, s.pm, robotFactory)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return s.evaluator != nil && s.evaluator.HasPermission(resource, action)
|
return s.evaluator != nil && s.evaluator.HasPermission(ctx, resource, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterRobotPolicies(p *models.Project, policies []*types.Policy) []*types.Policy {
|
||||||
|
namespace := rbac.NewProjectNamespace(p.ProjectID)
|
||||||
|
|
||||||
|
var results []*types.Policy
|
||||||
|
for _, policy := range policies {
|
||||||
|
if types.ResourceAllowedInNamespace(policy.Resource, namespace) {
|
||||||
|
results = append(results, policy)
|
||||||
|
// give the PUSH action a pull access
|
||||||
|
if policy.Action == rbac.ActionPush {
|
||||||
|
results = append(results, &types.Policy{Resource: policy.Resource, Action: rbac.ActionPull})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results
|
||||||
}
|
}
|
||||||
|
@ -15,19 +15,17 @@
|
|||||||
package robot
|
package robot
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/dao"
|
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/utils/test"
|
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
|
||||||
"github.com/goharbor/harbor/src/core/promgr/pmsdriver/local"
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||||
|
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||||
|
"github.com/goharbor/harbor/src/testing/mock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -36,64 +34,49 @@ var (
|
|||||||
Name: "testrobot",
|
Name: "testrobot",
|
||||||
OwnerID: 1,
|
OwnerID: 1,
|
||||||
}
|
}
|
||||||
pm = promgr.NewDefaultProjectManager(local.NewDriver(), true)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
|
||||||
test.InitDatabaseFromEnv()
|
|
||||||
|
|
||||||
// add project
|
|
||||||
id, err := dao.AddProject(*private)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to add project: %v", err)
|
|
||||||
}
|
|
||||||
private.ProjectID = id
|
|
||||||
defer dao.DeleteProject(id)
|
|
||||||
|
|
||||||
os.Exit(m.Run())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIsAuthenticated(t *testing.T) {
|
func TestIsAuthenticated(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil, nil)
|
ctx := NewSecurityContext(nil, nil)
|
||||||
assert.False(t, ctx.IsAuthenticated())
|
assert.False(t, ctx.IsAuthenticated())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&model.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil, nil)
|
}, nil)
|
||||||
assert.True(t, ctx.IsAuthenticated())
|
assert.True(t, ctx.IsAuthenticated())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUsername(t *testing.T) {
|
func TestGetUsername(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil, nil)
|
ctx := NewSecurityContext(nil, nil)
|
||||||
assert.Equal(t, "", ctx.GetUsername())
|
assert.Equal(t, "", ctx.GetUsername())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&model.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil, nil)
|
}, nil)
|
||||||
assert.Equal(t, "test", ctx.GetUsername())
|
assert.Equal(t, "test", ctx.GetUsername())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsSysAdmin(t *testing.T) {
|
func TestIsSysAdmin(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil, nil)
|
ctx := NewSecurityContext(nil, nil)
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
assert.False(t, ctx.IsSysAdmin())
|
||||||
|
|
||||||
// authenticated, non admin
|
// authenticated, non admin
|
||||||
ctx = NewSecurityContext(&model.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil, nil)
|
}, nil)
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
assert.False(t, ctx.IsSysAdmin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsSolutionUser(t *testing.T) {
|
func TestIsSolutionUser(t *testing.T) {
|
||||||
ctx := NewSecurityContext(nil, nil, nil)
|
ctx := NewSecurityContext(nil, nil)
|
||||||
assert.False(t, ctx.IsSolutionUser())
|
assert.False(t, ctx.IsSolutionUser())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,9 +92,13 @@ func TestHasPullPerm(t *testing.T) {
|
|||||||
Description: "desc",
|
Description: "desc",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := NewSecurityContext(robot, pm, policies)
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(robot, policies)
|
||||||
|
ctx.ctl = ctl
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
assert.True(t, ctx.Can(rbac.ActionPull, resource))
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasPushPerm(t *testing.T) {
|
func TestHasPushPerm(t *testing.T) {
|
||||||
@ -126,9 +113,13 @@ func TestHasPushPerm(t *testing.T) {
|
|||||||
Description: "desc",
|
Description: "desc",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := NewSecurityContext(robot, pm, policies)
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(robot, policies)
|
||||||
|
ctx.ctl = ctl
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
assert.True(t, ctx.Can(rbac.ActionPush, resource))
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasPushPullPerm(t *testing.T) {
|
func TestHasPushPullPerm(t *testing.T) {
|
||||||
@ -147,7 +138,56 @@ func TestHasPushPullPerm(t *testing.T) {
|
|||||||
Description: "desc",
|
Description: "desc",
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := NewSecurityContext(robot, pm, policies)
|
ctl := &projecttesting.Controller{}
|
||||||
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
|
ctx := NewSecurityContext(robot, policies)
|
||||||
|
ctx.ctl = ctl
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
assert.True(t, ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource))
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource) && ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_filterRobotPolicies(t *testing.T) {
|
||||||
|
type args struct {
|
||||||
|
p *models.Project
|
||||||
|
policies []*types.Policy
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []*types.Policy
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"policies of one project",
|
||||||
|
args{
|
||||||
|
&models.Project{ProjectID: 1},
|
||||||
|
[]*types.Policy{
|
||||||
|
{Resource: "/project/1/repository", Action: "pull", Effect: "allow"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*types.Policy{
|
||||||
|
{Resource: "/project/1/repository", Action: "pull", Effect: "allow"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"policies of multi projects",
|
||||||
|
args{
|
||||||
|
&models.Project{ProjectID: 1},
|
||||||
|
[]*types.Policy{
|
||||||
|
{Resource: "/project/1/repository", Action: "pull", Effect: "allow"},
|
||||||
|
{Resource: "/project/2/repository", Action: "pull", Effect: "allow"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[]*types.Policy{
|
||||||
|
{Resource: "/project/1/repository", Action: "pull", Effect: "allow"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := filterRobotPolicies(tt.args.p, tt.args.policies); !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("filterRobotPolicies() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
package robot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// robot implement the rbac.User interface for project robot account
|
|
||||||
type robot struct {
|
|
||||||
username string
|
|
||||||
namespace types.Namespace
|
|
||||||
policies []*types.Policy
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserName get the robot name.
|
|
||||||
func (r *robot) GetUserName() string {
|
|
||||||
return r.username
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPolicies ...
|
|
||||||
func (r *robot) GetPolicies() []*types.Policy {
|
|
||||||
return r.policies
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRoles robot has no definition of role, always return nil here.
|
|
||||||
func (r *robot) GetRoles() []types.RBACRole {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRobot ...
|
|
||||||
func NewRobot(username string, namespace types.Namespace, policies []*types.Policy) types.RBACUser {
|
|
||||||
return &robot{
|
|
||||||
username: username,
|
|
||||||
namespace: namespace,
|
|
||||||
policies: filterPolicies(namespace, policies),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func filterPolicies(namespace types.Namespace, policies []*types.Policy) []*types.Policy {
|
|
||||||
var results []*types.Policy
|
|
||||||
for _, policy := range policies {
|
|
||||||
if types.ResourceAllowedInNamespace(policy.Resource, namespace) {
|
|
||||||
results = append(results, policy)
|
|
||||||
// give the PUSH action a pull access
|
|
||||||
if policy.Action == rbac.ActionPush {
|
|
||||||
results = append(results, &types.Policy{Resource: policy.Resource, Action: rbac.ActionPull})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results
|
|
||||||
}
|
|
@ -1,55 +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 robot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetPolicies(t *testing.T) {
|
|
||||||
|
|
||||||
rbacPolicy := &types.Policy{
|
|
||||||
Resource: "/project/libray/repository",
|
|
||||||
Action: "pull",
|
|
||||||
}
|
|
||||||
policies := []*types.Policy{}
|
|
||||||
policies = append(policies, rbacPolicy)
|
|
||||||
|
|
||||||
robot := robot{
|
|
||||||
username: "test",
|
|
||||||
namespace: rbac.NewProjectNamespace(1),
|
|
||||||
policies: policies,
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, robot.GetUserName(), "test")
|
|
||||||
assert.NotNil(t, robot.GetPolicies())
|
|
||||||
assert.Nil(t, robot.GetRoles())
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewRobot(t *testing.T) {
|
|
||||||
policies := []*types.Policy{
|
|
||||||
{Resource: "/project/1/repository", Action: "push"},
|
|
||||||
{Resource: "/project/1/repository", Action: "scanner-pull"},
|
|
||||||
{Resource: "/project/library/repository", Action: "pull"},
|
|
||||||
{Resource: "/project/library/repository", Action: "push"},
|
|
||||||
}
|
|
||||||
|
|
||||||
robot := NewRobot("test", rbac.NewProjectNamespace(1), policies)
|
|
||||||
assert.Len(t, robot.GetPolicies(), 3)
|
|
||||||
}
|
|
@ -15,6 +15,8 @@
|
|||||||
package secret
|
package secret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/secret"
|
"github.com/goharbor/harbor/src/common/secret"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
@ -75,7 +77,7 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
|||||||
// Can returns whether the user can do action on resource
|
// Can returns whether the user can do action on resource
|
||||||
// returns true if the corresponding user of the secret
|
// returns true if the corresponding user of the secret
|
||||||
// is jobservice or core service, otherwise returns false
|
// is jobservice or core service, otherwise returns false
|
||||||
func (s *SecurityContext) Can(action types.Action, resource types.Resource) bool {
|
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
if s.store == nil {
|
if s.store == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package secret
|
package secret
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
libctx "context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
@ -99,7 +100,7 @@ func TestHasPullPerm(t *testing.T) {
|
|||||||
resource := rbac.Resource("/project/project_name/repository")
|
resource := rbac.Resource("/project/project_name/repository")
|
||||||
// secret store is null
|
// secret store is null
|
||||||
context := NewSecurityContext("", nil)
|
context := NewSecurityContext("", nil)
|
||||||
hasReadPerm := context.Can(rbac.ActionPull, resource)
|
hasReadPerm := context.Can(libctx.TODO(), rbac.ActionPull, resource)
|
||||||
assert.False(t, hasReadPerm)
|
assert.False(t, hasReadPerm)
|
||||||
|
|
||||||
// invalid secret
|
// invalid secret
|
||||||
@ -107,7 +108,7 @@ func TestHasPullPerm(t *testing.T) {
|
|||||||
secret.NewStore(map[string]string{
|
secret.NewStore(map[string]string{
|
||||||
"jobservice_secret": secret.JobserviceUser,
|
"jobservice_secret": secret.JobserviceUser,
|
||||||
}))
|
}))
|
||||||
hasReadPerm = context.Can(rbac.ActionPull, resource)
|
hasReadPerm = context.Can(libctx.TODO(), rbac.ActionPull, resource)
|
||||||
assert.False(t, hasReadPerm)
|
assert.False(t, hasReadPerm)
|
||||||
|
|
||||||
// valid secret, project name
|
// valid secret, project name
|
||||||
@ -115,12 +116,12 @@ func TestHasPullPerm(t *testing.T) {
|
|||||||
secret.NewStore(map[string]string{
|
secret.NewStore(map[string]string{
|
||||||
"jobservice_secret": secret.JobserviceUser,
|
"jobservice_secret": secret.JobserviceUser,
|
||||||
}))
|
}))
|
||||||
hasReadPerm = context.Can(rbac.ActionPull, resource)
|
hasReadPerm = context.Can(libctx.TODO(), rbac.ActionPull, resource)
|
||||||
assert.True(t, hasReadPerm)
|
assert.True(t, hasReadPerm)
|
||||||
|
|
||||||
// valid secret, project ID
|
// valid secret, project ID
|
||||||
resource = rbac.Resource("/project/1/repository")
|
resource = rbac.Resource("/project/1/repository")
|
||||||
hasReadPerm = context.Can(rbac.ActionPull, resource)
|
hasReadPerm = context.Can(libctx.TODO(), rbac.ActionPull, resource)
|
||||||
assert.True(t, hasReadPerm)
|
assert.True(t, hasReadPerm)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,11 +133,11 @@ func TestHasPushPerm(t *testing.T) {
|
|||||||
|
|
||||||
// project name
|
// project name
|
||||||
resource := rbac.Resource("/project/project_name/repository")
|
resource := rbac.Resource("/project/project_name/repository")
|
||||||
assert.False(t, context.Can(rbac.ActionPush, resource))
|
assert.False(t, context.Can(libctx.TODO(), rbac.ActionPush, resource))
|
||||||
|
|
||||||
// project ID
|
// project ID
|
||||||
resource = rbac.Resource("/project/1/repository")
|
resource = rbac.Resource("/project/1/repository")
|
||||||
assert.False(t, context.Can(rbac.ActionPush, resource))
|
assert.False(t, context.Can(libctx.TODO(), rbac.ActionPush, resource))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasPushPullPerm(t *testing.T) {
|
func TestHasPushPullPerm(t *testing.T) {
|
||||||
@ -147,9 +148,9 @@ func TestHasPushPullPerm(t *testing.T) {
|
|||||||
|
|
||||||
// project name
|
// project name
|
||||||
resource := rbac.Resource("/project/project_name/repository")
|
resource := rbac.Resource("/project/project_name/repository")
|
||||||
assert.False(t, context.Can(rbac.ActionPush, resource) && context.Can(rbac.ActionPull, resource))
|
assert.False(t, context.Can(libctx.TODO(), rbac.ActionPush, resource) && context.Can(libctx.TODO(), rbac.ActionPull, resource))
|
||||||
|
|
||||||
// project ID
|
// project ID
|
||||||
resource = rbac.Resource("/project/1/repository")
|
resource = rbac.Resource("/project/1/repository")
|
||||||
assert.False(t, context.Can(rbac.ActionPush, resource) && context.Can(rbac.ActionPull, resource))
|
assert.False(t, context.Can(libctx.TODO(), rbac.ActionPush, resource) && context.Can(libctx.TODO(), rbac.ActionPull, resource))
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,9 @@ import (
|
|||||||
registry_token "github.com/docker/distribution/registry/auth/token"
|
registry_token "github.com/docker/distribution/registry/auth/token"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/project"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/project/models"
|
"github.com/goharbor/harbor/src/pkg/project/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ type tokenSecurityCtx struct {
|
|||||||
logger *log.Logger
|
logger *log.Logger
|
||||||
name string
|
name string
|
||||||
accessMap map[string]map[types.Action]struct{}
|
accessMap map[string]map[types.Action]struct{}
|
||||||
getProject func(int64) (*models.Project, error)
|
ctl project.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenSecurityCtx) Name() string {
|
func (t *tokenSecurityCtx) Name() string {
|
||||||
@ -51,7 +51,7 @@ func (t *tokenSecurityCtx) GetProjectRoles(projectIDOrName interface{}) []int {
|
|||||||
return []int{}
|
return []int{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tokenSecurityCtx) Can(action types.Action, resource types.Resource) bool {
|
func (t *tokenSecurityCtx) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
if !strings.HasSuffix(resource.String(), rbac.ResourceRepository.String()) {
|
if !strings.HasSuffix(resource.String(), rbac.ResourceRepository.String()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -65,7 +65,7 @@ func (t *tokenSecurityCtx) Can(action types.Action, resource types.Resource) boo
|
|||||||
t.logger.Warningf("Failed to get project id from namespace: %s", ns)
|
t.logger.Warningf("Failed to get project id from namespace: %s", ns)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
p, err := t.getProject(pid)
|
p, err := t.ctl.Get(ctx, pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.logger.Warningf("Failed to get project, id: %d, error: %v", pid, err)
|
t.logger.Warningf("Failed to get project, id: %d, error: %v", pid, err)
|
||||||
return false
|
return false
|
||||||
@ -114,8 +114,6 @@ func New(ctx context.Context, name string, access []*registry_token.ResourceActi
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
name: name,
|
name: name,
|
||||||
accessMap: m,
|
accessMap: m,
|
||||||
getProject: func(id int64) (*models.Project, error) {
|
ctl: project.Ctl,
|
||||||
return project.Mgr.Get(ctx, id)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,17 +22,17 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/project/models"
|
"github.com/goharbor/harbor/src/pkg/project/models"
|
||||||
"github.com/goharbor/harbor/src/testing/pkg/project"
|
"github.com/goharbor/harbor/src/testing/controller/project"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAll(t *testing.T) {
|
func TestAll(t *testing.T) {
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
|
|
||||||
mgr := &project.Manager{}
|
ctl := &project.Controller{}
|
||||||
mgr.On("Get", ctx, int64(1)).Return(&models.Project{ProjectID: 1, Name: "library"}, nil)
|
ctl.On("Get", ctx, int64(1)).Return(&models.Project{ProjectID: 1, Name: "library"}, nil)
|
||||||
mgr.On("Get", ctx, int64(2)).Return(&models.Project{ProjectID: 2, Name: "test"}, nil)
|
ctl.On("Get", ctx, int64(2)).Return(&models.Project{ProjectID: 2, Name: "test"}, nil)
|
||||||
mgr.On("Get", ctx, int64(3)).Return(&models.Project{ProjectID: 3, Name: "development"}, nil)
|
ctl.On("Get", ctx, int64(3)).Return(&models.Project{ProjectID: 3, Name: "development"}, nil)
|
||||||
|
|
||||||
access := []*token.ResourceActions{
|
access := []*token.ResourceActions{
|
||||||
{
|
{
|
||||||
@ -63,9 +63,7 @@ func TestAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
sc := New(context.Background(), "jack", access)
|
sc := New(context.Background(), "jack", access)
|
||||||
tsc := sc.(*tokenSecurityCtx)
|
tsc := sc.(*tokenSecurityCtx)
|
||||||
tsc.getProject = func(id int64) (*models.Project, error) {
|
tsc.ctl = ctl
|
||||||
return mgr.Get(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
resource types.Resource
|
resource types.Resource
|
||||||
@ -115,6 +113,6 @@ func TestAll(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
assert.Equal(t, c.expect, sc.Can(c.action, c.resource))
|
assert.Equal(t, c.expect, sc.Can(ctx, c.action, c.resource))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,8 @@ type Controller interface {
|
|||||||
List(ctx context.Context, query *q.Query, options ...Option) ([]*models.Project, error)
|
List(ctx context.Context, query *q.Query, options ...Option) ([]*models.Project, error)
|
||||||
// Update update the project
|
// Update update the project
|
||||||
Update(ctx context.Context, project *models.Project) error
|
Update(ctx context.Context, project *models.Project) error
|
||||||
|
// ListRoles lists the roles of user for the specific project
|
||||||
|
ListRoles(ctx context.Context, projectID int64, u *user.User) ([]int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewController creates an instance of the default project controller
|
// NewController creates an instance of the default project controller
|
||||||
@ -226,6 +228,10 @@ func (c *controller) Update(ctx context.Context, p *models.Project) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *controller) ListRoles(ctx context.Context, projectID int64, u *user.User) ([]int, error) {
|
||||||
|
return c.projectMgr.ListRoles(ctx, projectID, u.UserID, u.GroupIDs...)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *controller) assembleProjects(ctx context.Context, projects models.Projects, options ...Option) error {
|
func (c *controller) assembleProjects(ctx context.Context, projects models.Projects, options ...Option) error {
|
||||||
opts := newOptions(options...)
|
opts := newOptions(options...)
|
||||||
if opts.WithMetadata {
|
if opts.WithMetadata {
|
||||||
|
@ -105,7 +105,7 @@ func (b *BaseController) HasProjectPermission(projectIDOrName interface{}, actio
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(subresource...)
|
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(subresource...)
|
||||||
if !b.SecurityCtx.Can(action, resource) {
|
if !b.SecurityCtx.Can(b.Ctx.Request.Context(), action, resource) {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,9 +486,10 @@ func (ua *UserAPI) ListUserPermissions() {
|
|||||||
scope := rbac.Resource(ua.Ctx.Input.Query("scope"))
|
scope := rbac.Resource(ua.Ctx.Input.Query("scope"))
|
||||||
policies := []*types.Policy{}
|
policies := []*types.Policy{}
|
||||||
|
|
||||||
|
ctx := ua.Ctx.Request.Context()
|
||||||
if ns, ok := types.NamespaceFromResource(scope); ok {
|
if ns, ok := types.NamespaceFromResource(scope); ok {
|
||||||
for _, policy := range ns.GetPolicies() {
|
for _, policy := range ns.GetPolicies() {
|
||||||
if ua.SecurityCtx.Can(policy.Action, policy.Resource) {
|
if ua.SecurityCtx.Can(ctx, policy.Action, policy.Resource) {
|
||||||
policies = append(policies, policy)
|
policies = append(policies, policy)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -24,11 +26,11 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
"github.com/goharbor/harbor/src/common/utils"
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
tokenpkg "github.com/goharbor/harbor/src/pkg/token"
|
tokenpkg "github.com/goharbor/harbor/src/pkg/token"
|
||||||
"github.com/goharbor/harbor/src/pkg/token/claims/v2"
|
v2 "github.com/goharbor/harbor/src/pkg/token/claims/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -81,8 +83,12 @@ func GetResourceActions(scopes []string) []*token.ResourceActions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions.
|
// filterAccess iterate a list of resource actions and try to use the filter that matches the resource type to filter the actions.
|
||||||
func filterAccess(access []*token.ResourceActions, ctx security.Context,
|
func filterAccess(ctx context.Context, access []*token.ResourceActions,
|
||||||
pm promgr.ProjectManager, filters map[string]accessFilter) error {
|
ctl project.Controller, filters map[string]accessFilter) error {
|
||||||
|
secCtx, ok := security.FromContext(ctx)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("failed to get security context from request")
|
||||||
|
}
|
||||||
var err error
|
var err error
|
||||||
for _, a := range access {
|
for _, a := range access {
|
||||||
f, ok := filters[a.Type]
|
f, ok := filters[a.Type]
|
||||||
@ -91,8 +97,8 @@ func filterAccess(access []*token.ResourceActions, ctx security.Context,
|
|||||||
log.Warningf("No filter found for access type: %s, skip filter, the access of resource '%s' will be set empty.", a.Type, a.Name)
|
log.Warningf("No filter found for access type: %s, skip filter, the access of resource '%s' will be set empty.", a.Type, a.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
err = f.filter(ctx, pm, a)
|
err = f.filter(ctx, ctl, a)
|
||||||
log.Debugf("user: %s, access: %v", ctx.GetUsername(), a)
|
log.Debugf("user: %s, access: %v", secCtx.GetUsername(), a)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to handle the resource %s:%s, due to error %v, returning empty access for it.",
|
log.Errorf("Failed to handle the resource %s:%s, due to error %v, returning empty access for it.",
|
||||||
a.Type, a.Name, err)
|
a.Type, a.Name, err)
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -24,8 +25,9 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,19 +129,21 @@ func parseImg(s string) (*image, error) {
|
|||||||
|
|
||||||
// An accessFilter will filter access based on userinfo
|
// An accessFilter will filter access based on userinfo
|
||||||
type accessFilter interface {
|
type accessFilter interface {
|
||||||
filter(ctx security.Context, pm promgr.ProjectManager, a *token.ResourceActions) error
|
filter(ctx context.Context, ctl project.Controller, a *token.ResourceActions) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type registryFilter struct {
|
type registryFilter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (reg registryFilter) filter(ctx security.Context, pm promgr.ProjectManager,
|
func (reg registryFilter) filter(ctx context.Context, ctl project.Controller,
|
||||||
a *token.ResourceActions) error {
|
a *token.ResourceActions) error {
|
||||||
// Do not filter if the request is to access registry catalog
|
// Do not filter if the request is to access registry catalog
|
||||||
if a.Name != "catalog" {
|
if a.Name != "catalog" {
|
||||||
return fmt.Errorf("Unable to handle, type: %s, name: %s", a.Type, a.Name)
|
return fmt.Errorf("Unable to handle, type: %s, name: %s", a.Type, a.Name)
|
||||||
}
|
}
|
||||||
if !ctx.IsSysAdmin() {
|
|
||||||
|
secCtx, ok := security.FromContext(ctx)
|
||||||
|
if !ok || !secCtx.IsSysAdmin() {
|
||||||
// Set the actions to empty is the user is not admin
|
// Set the actions to empty is the user is not admin
|
||||||
a.Actions = []string{}
|
a.Actions = []string{}
|
||||||
}
|
}
|
||||||
@ -151,7 +155,7 @@ type repositoryFilter struct {
|
|||||||
parser imageParser
|
parser imageParser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManager,
|
func (rep repositoryFilter) filter(ctx context.Context, ctl project.Controller,
|
||||||
a *token.ResourceActions) error {
|
a *token.ResourceActions) error {
|
||||||
// clear action list to assign to new acess element after perm check.
|
// clear action list to assign to new acess element after perm check.
|
||||||
img, err := rep.parser.parse(a.Name)
|
img, err := rep.parser.parse(a.Name)
|
||||||
@ -161,24 +165,26 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManage
|
|||||||
projectName := img.namespace
|
projectName := img.namespace
|
||||||
permission := ""
|
permission := ""
|
||||||
|
|
||||||
project, err := pm.Get(projectName)
|
project, err := ctl.GetByName(ctx, projectName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
if errors.IsNotFoundErr(err) {
|
||||||
}
|
|
||||||
if project == nil {
|
|
||||||
log.Debugf("project %s does not exist, set empty permission", projectName)
|
log.Debugf("project %s does not exist, set empty permission", projectName)
|
||||||
a.Actions = []string{}
|
a.Actions = []string{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
secCtx, _ := security.FromContext(ctx)
|
||||||
|
|
||||||
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
if ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource) {
|
if secCtx.Can(ctx, rbac.ActionPush, resource) && secCtx.Can(ctx, rbac.ActionPull, resource) {
|
||||||
permission = "RWM"
|
permission = "RWM"
|
||||||
} else if ctx.Can(rbac.ActionPush, resource) {
|
} else if secCtx.Can(ctx, rbac.ActionPush, resource) {
|
||||||
permission = "RW"
|
permission = "RW"
|
||||||
} else if ctx.Can(rbac.ActionScannerPull, resource) {
|
} else if secCtx.Can(ctx, rbac.ActionScannerPull, resource) {
|
||||||
permission = "RS"
|
permission = "RS"
|
||||||
} else if ctx.Can(rbac.ActionPull, resource) {
|
} else if secCtx.Can(ctx, rbac.ActionPull, resource) {
|
||||||
permission = "R"
|
permission = "R"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,8 +213,6 @@ func (g generalCreator) Create(r *http.Request) (*models.Token, error) {
|
|||||||
return nil, fmt.Errorf("failed to get security context from request")
|
return nil, fmt.Errorf("failed to get security context from request")
|
||||||
}
|
}
|
||||||
|
|
||||||
pm := config.GlobalProjectMgr
|
|
||||||
|
|
||||||
// for docker login
|
// for docker login
|
||||||
if !ctx.IsAuthenticated() {
|
if !ctx.IsAuthenticated() {
|
||||||
if len(scopes) == 0 {
|
if len(scopes) == 0 {
|
||||||
@ -216,7 +220,7 @@ func (g generalCreator) Create(r *http.Request) (*models.Token, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
access := GetResourceActions(scopes)
|
access := GetResourceActions(scopes)
|
||||||
err = filterAccess(access, ctx, pm, g.filterMap)
|
err = filterAccess(r.Context(), access, project.Ctl, g.filterMap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,7 @@
|
|||||||
package token
|
package token
|
||||||
|
|
||||||
import (
|
import (
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
"context"
|
||||||
"github.com/docker/distribution/registry/auth/token"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
@ -29,9 +26,13 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
"github.com/docker/distribution/registry/auth/token"
|
||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
@ -243,7 +244,7 @@ func (f *fakeSecurityContext) IsSysAdmin() bool {
|
|||||||
func (f *fakeSecurityContext) IsSolutionUser() bool {
|
func (f *fakeSecurityContext) IsSolutionUser() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (f *fakeSecurityContext) Can(action rbac.Action, resource rbac.Resource) bool {
|
func (f *fakeSecurityContext) Can(ctx context.Context, action rbac.Action, resource rbac.Resource) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
func (f *fakeSecurityContext) GetMyProjects() ([]*models.Project, error) {
|
func (f *fakeSecurityContext) GetMyProjects() ([]*models.Project, error) {
|
||||||
@ -271,21 +272,26 @@ func TestFilterAccess(t *testing.T) {
|
|||||||
Name: "catalog",
|
Name: "catalog",
|
||||||
Actions: []string{},
|
Actions: []string{},
|
||||||
}
|
}
|
||||||
err = filterAccess(a1, &fakeSecurityContext{
|
|
||||||
|
ctx := func(secCtx security.Context) context.Context {
|
||||||
|
return security.NewContext(context.TODO(), secCtx)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = filterAccess(ctx(&fakeSecurityContext{
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
}, nil, registryFilterMap)
|
}), a1, nil, registryFilterMap)
|
||||||
assert.Nil(t, err, "Unexpected error: %v", err)
|
assert.Nil(t, err, "Unexpected error: %v", err)
|
||||||
assert.Equal(t, ra1, *a1[0], "Mismatch after registry filter Map")
|
assert.Equal(t, ra1, *a1[0], "Mismatch after registry filter Map")
|
||||||
|
|
||||||
err = filterAccess(a2, &fakeSecurityContext{
|
err = filterAccess(ctx(&fakeSecurityContext{
|
||||||
isAdmin: true,
|
isAdmin: true,
|
||||||
}, nil, notaryFilterMap)
|
}), a2, nil, notaryFilterMap)
|
||||||
assert.Nil(t, err, "Unexpected error: %v", err)
|
assert.Nil(t, err, "Unexpected error: %v", err)
|
||||||
assert.Equal(t, ra2, *a2[0], "Mismatch after notary filter Map")
|
assert.Equal(t, ra2, *a2[0], "Mismatch after notary filter Map")
|
||||||
|
|
||||||
err = filterAccess(a3, &fakeSecurityContext{
|
err = filterAccess(ctx(&fakeSecurityContext{
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
}, nil, registryFilterMap)
|
}), a3, nil, registryFilterMap)
|
||||||
assert.Nil(t, err, "Unexpected error: %v", err)
|
assert.Nil(t, err, "Unexpected error: %v", err)
|
||||||
assert.Equal(t, ra2, *a3[0], "Mismatch after registry filter Map")
|
assert.Equal(t, ra2, *a3[0], "Mismatch after registry filter Map")
|
||||||
}
|
}
|
||||||
|
@ -27,10 +27,19 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/lib/q"
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Params ...
|
// NewCondition alias function of orm.NewCondition
|
||||||
|
var NewCondition = orm.NewCondition
|
||||||
|
|
||||||
|
// Condition alias to orm.Condition
|
||||||
|
type Condition = orm.Condition
|
||||||
|
|
||||||
|
// Params alias to orm.Params
|
||||||
type Params = orm.Params
|
type Params = orm.Params
|
||||||
|
|
||||||
// QuerySeter ...
|
// ParamsList alias to orm.ParamsList
|
||||||
|
type ParamsList = orm.ParamsList
|
||||||
|
|
||||||
|
// QuerySeter alias to orm.QuerySeter
|
||||||
type QuerySeter = orm.QuerySeter
|
type QuerySeter = orm.QuerySeter
|
||||||
|
|
||||||
// Escape special characters
|
// Escape special characters
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
@ -28,7 +30,7 @@ type Evaluator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasPermission always return true for the system administrator
|
// HasPermission always return true for the system administrator
|
||||||
func (e *Evaluator) HasPermission(resource types.Resource, action types.Action) bool {
|
func (e *Evaluator) HasPermission(ctx context.Context, resource types.Resource, action types.Action) bool {
|
||||||
log.Debugf("system administrator %s require %s action for resource %s", e.username, action, resource)
|
log.Debugf("system administrator %s require %s action for resource %s", e.username, action, resource)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -15,13 +15,15 @@
|
|||||||
package evaluator
|
package evaluator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Evaluator the permission evaluator
|
// Evaluator the permission evaluator
|
||||||
type Evaluator interface {
|
type Evaluator interface {
|
||||||
// HasPermission returns true when user has action permission for the resource
|
// HasPermission returns true when user has action permission for the resource
|
||||||
HasPermission(resource types.Resource, action types.Action) bool
|
HasPermission(ctx context.Context, resource types.Resource, action types.Action) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluators evaluator set
|
// Evaluators evaluator set
|
||||||
@ -53,9 +55,9 @@ func (evaluators Evaluators) Add(newEvaluators ...Evaluator) Evaluators {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasPermission returns true when one of evaluator has action permission for the resource
|
// HasPermission returns true when one of evaluator has action permission for the resource
|
||||||
func (evaluators Evaluators) HasPermission(resource types.Resource, action types.Action) bool {
|
func (evaluators Evaluators) HasPermission(ctx context.Context, resource types.Resource, action types.Action) bool {
|
||||||
for _, evaluator := range evaluators {
|
for _, evaluator := range evaluators {
|
||||||
if evaluator != nil && evaluator.HasPermission(resource, action) {
|
if evaluator != nil && evaluator.HasPermission(ctx, resource, action) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package evaluator
|
package evaluator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
@ -25,7 +26,7 @@ type mockEvaluator struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *mockEvaluator) HasPermission(resource types.Resource, action types.Action) bool {
|
func (e *mockEvaluator) HasPermission(ctx context.Context, resource types.Resource, action types.Action) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package lazy
|
package lazy
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
||||||
@ -34,12 +35,12 @@ type Evaluator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasPermission returns true when user has action permission for the resource
|
// HasPermission returns true when user has action permission for the resource
|
||||||
func (l *Evaluator) HasPermission(resource types.Resource, action types.Action) bool {
|
func (l *Evaluator) HasPermission(ctx context.Context, resource types.Resource, action types.Action) bool {
|
||||||
l.once.Do(func() {
|
l.once.Do(func() {
|
||||||
l.evaluator = l.factory()
|
l.evaluator = l.factory()
|
||||||
})
|
})
|
||||||
|
|
||||||
return l.evaluator != nil && l.evaluator.HasPermission(resource, action)
|
return l.evaluator != nil && l.evaluator.HasPermission(ctx, resource, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns lazy evaluator
|
// New returns lazy evaluator
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package namespace
|
package namespace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// EvaluatorFactory returns the evaluator.Evaluator of the namespace
|
// EvaluatorFactory returns the evaluator.Evaluator of the namespace
|
||||||
type EvaluatorFactory func(types.Namespace) evaluator.Evaluator
|
type EvaluatorFactory func(context.Context, types.Namespace) evaluator.Evaluator
|
||||||
|
|
||||||
var _ evaluator.Evaluator = &Evaluator{}
|
var _ evaluator.Evaluator = &Evaluator{}
|
||||||
|
|
||||||
@ -35,21 +36,21 @@ type Evaluator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasPermission returns true when user has action permission for the resource
|
// HasPermission returns true when user has action permission for the resource
|
||||||
func (e *Evaluator) HasPermission(resource types.Resource, action types.Action) bool {
|
func (e *Evaluator) HasPermission(ctx context.Context, resource types.Resource, action types.Action) bool {
|
||||||
ns, ok := types.NamespaceFromResource(resource)
|
ns, ok := types.NamespaceFromResource(resource)
|
||||||
if ok && ns.Kind() == e.namespaceKind {
|
if ok && ns.Kind() == e.namespaceKind {
|
||||||
var eva evaluator.Evaluator
|
var eva evaluator.Evaluator
|
||||||
|
|
||||||
key := fmt.Sprintf("%s:%v", ns.Kind(), ns.Identity())
|
key := fmt.Sprintf("%p:%s:%v", ctx, ns.Kind(), ns.Identity())
|
||||||
value, ok := e.cache.Load(key)
|
value, ok := e.cache.Load(key)
|
||||||
if !ok {
|
if !ok {
|
||||||
eva = e.factory(ns)
|
eva = e.factory(ctx, ns)
|
||||||
e.cache.Store(key, eva)
|
e.cache.Store(key, eva)
|
||||||
} else {
|
} else {
|
||||||
eva, _ = value.(evaluator.Evaluator) // maybe value is nil
|
eva, _ = value.(evaluator.Evaluator) // maybe value is nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return eva != nil && eva.HasPermission(resource, action)
|
return eva != nil && eva.HasPermission(ctx, resource, action)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package rbac
|
package rbac
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/casbin/casbin"
|
"github.com/casbin/casbin"
|
||||||
@ -32,7 +33,7 @@ type Evaluator struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasPermission returns true when the rbac user has action permission for the resource
|
// HasPermission returns true when the rbac user has action permission for the resource
|
||||||
func (e *Evaluator) HasPermission(resource types.Resource, action types.Action) bool {
|
func (e *Evaluator) HasPermission(ctx context.Context, resource types.Resource, action types.Action) bool {
|
||||||
e.once.Do(func() {
|
e.once.Do(func() {
|
||||||
e.enforcer = makeEnforcer(e.rbacUser)
|
e.enforcer = makeEnforcer(e.rbacUser)
|
||||||
})
|
})
|
||||||
|
@ -39,6 +39,8 @@ type DAO interface {
|
|||||||
GetByName(ctx context.Context, name string) (*models.Project, error)
|
GetByName(ctx context.Context, name string) (*models.Project, error)
|
||||||
// List list projects
|
// List list projects
|
||||||
List(ctx context.Context, query *q.Query) ([]*models.Project, error)
|
List(ctx context.Context, query *q.Query) ([]*models.Project, error)
|
||||||
|
// Lists the roles of user for the specific project
|
||||||
|
ListRoles(ctx context.Context, projectID int64, userID int, groupIDs ...int) ([]int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns an instance of the default DAO
|
// New returns an instance of the default DAO
|
||||||
@ -173,3 +175,34 @@ func (d *dao) List(ctx context.Context, query *q.Query) ([]*models.Project, erro
|
|||||||
|
|
||||||
return projects, nil
|
return projects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *dao) ListRoles(ctx context.Context, projectID int64, userID int, groupIDs ...int) ([]int, error) {
|
||||||
|
qs, err := orm.QuerySetter(ctx, &Member{}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
conds := []*orm.Condition{
|
||||||
|
orm.NewCondition().And("entity_type", "u").And("project_id", projectID).And("entity_id", userID),
|
||||||
|
}
|
||||||
|
if len(groupIDs) > 0 {
|
||||||
|
conds = append(conds, orm.NewCondition().And("entity_type", "g").And("project_id", projectID).And("entity_id__in", groupIDs))
|
||||||
|
}
|
||||||
|
|
||||||
|
cond := orm.NewCondition()
|
||||||
|
for _, c := range conds {
|
||||||
|
cond = cond.OrCond(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
var values orm.ParamsList
|
||||||
|
if _, err := qs.SetCond(cond).ValuesFlat(&values, "role"); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var roles []int
|
||||||
|
for _, value := range values {
|
||||||
|
roles = append(roles, int(value.(int64)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles, nil
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
"github.com/goharbor/harbor/src/lib/orm"
|
"github.com/goharbor/harbor/src/lib/orm"
|
||||||
"github.com/goharbor/harbor/src/lib/q"
|
"github.com/goharbor/harbor/src/lib/q"
|
||||||
@ -339,6 +340,58 @@ func (suite *DaoTestSuite) TestListByMember() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *DaoTestSuite) TestListRoles() {
|
||||||
|
{
|
||||||
|
// only projectAdmin
|
||||||
|
suite.WithUser(func(userID int64, username string) {
|
||||||
|
project := &models.Project{
|
||||||
|
Name: utils.GenerateRandomString(),
|
||||||
|
OwnerID: int(userID),
|
||||||
|
}
|
||||||
|
projectID, err := suite.dao.Create(orm.Context(), project)
|
||||||
|
suite.Nil(err)
|
||||||
|
defer suite.dao.Delete(orm.Context(), projectID)
|
||||||
|
|
||||||
|
roles, err := suite.dao.ListRoles(orm.Context(), projectID, int(userID))
|
||||||
|
suite.Nil(err)
|
||||||
|
suite.Len(roles, 1)
|
||||||
|
suite.Contains(roles, common.RoleProjectAdmin)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// projectAdmin and user groups
|
||||||
|
suite.WithUser(func(userID int64, username string) {
|
||||||
|
project := &models.Project{
|
||||||
|
Name: utils.GenerateRandomString(),
|
||||||
|
OwnerID: int(userID),
|
||||||
|
}
|
||||||
|
projectID, err := suite.dao.Create(orm.Context(), project)
|
||||||
|
suite.Nil(err)
|
||||||
|
|
||||||
|
defer suite.dao.Delete(orm.Context(), projectID)
|
||||||
|
|
||||||
|
suite.WithUserGroup(func(groupID int64, groupName string) {
|
||||||
|
|
||||||
|
o, err := orm.FromContext(orm.Context())
|
||||||
|
if err != nil {
|
||||||
|
suite.Fail("got error %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var pid int64
|
||||||
|
suite.Nil(o.Raw("INSERT INTO project_member (project_id, entity_id, role, entity_type) values (?, ?, ?, ?) RETURNING id", projectID, groupID, common.RoleGuest, "g").QueryRow(&pid))
|
||||||
|
defer o.Raw("DELETE FROM project_member WHERE id = ?", pid)
|
||||||
|
|
||||||
|
roles, err := suite.dao.ListRoles(orm.Context(), projectID, int(userID), int(groupID))
|
||||||
|
suite.Nil(err)
|
||||||
|
suite.Len(roles, 2)
|
||||||
|
suite.Contains(roles, common.RoleProjectAdmin)
|
||||||
|
suite.Contains(roles, common.RoleGuest)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDaoTestSuite(t *testing.T) {
|
func TestDaoTestSuite(t *testing.T) {
|
||||||
suite.Run(t, &DaoTestSuite{})
|
suite.Run(t, &DaoTestSuite{})
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,9 @@ type Manager interface {
|
|||||||
|
|
||||||
// List projects according to the query
|
// List projects according to the query
|
||||||
List(ctx context.Context, query *q.Query) ([]*models.Project, error)
|
List(ctx context.Context, query *q.Query) ([]*models.Project, error)
|
||||||
|
|
||||||
|
// ListRoles returns the roles of user for the specific project
|
||||||
|
ListRoles(ctx context.Context, projectID int64, userID int, groupIDs ...int) ([]int, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a default implementation of Manager
|
// New returns a default implementation of Manager
|
||||||
@ -111,3 +114,8 @@ func (m *manager) Get(ctx context.Context, idOrName interface{}) (*models.Projec
|
|||||||
func (m *manager) List(ctx context.Context, query *q.Query) ([]*models.Project, error) {
|
func (m *manager) List(ctx context.Context, query *q.Query) ([]*models.Project, error) {
|
||||||
return m.dao.List(ctx, query)
|
return m.dao.List(ctx, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lists the roles of user for the specific project
|
||||||
|
func (m *manager) ListRoles(ctx context.Context, projectID int64, userID int, groupIDs ...int) ([]int, error) {
|
||||||
|
return m.dao.ListRoles(ctx, projectID, userID, groupIDs...)
|
||||||
|
}
|
||||||
|
@ -24,6 +24,9 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/pkg/user/models"
|
"github.com/goharbor/harbor/src/pkg/user/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// User alias to models.User
|
||||||
|
type User = models.User
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// Mgr is the global project manager
|
// Mgr is the global project manager
|
||||||
Mgr = New()
|
Mgr = New()
|
||||||
|
@ -21,15 +21,13 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
"github.com/goharbor/harbor/src/common/security/proxycachesecret"
|
"github.com/goharbor/harbor/src/common/security/proxycachesecret"
|
||||||
securitySecret "github.com/goharbor/harbor/src/common/security/secret"
|
securitySecret "github.com/goharbor/harbor/src/common/security/secret"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsProxySession(t *testing.T) {
|
func TestIsProxySession(t *testing.T) {
|
||||||
config.Init()
|
sc1 := securitySecret.NewSecurityContext("123456789", nil)
|
||||||
sc1 := securitySecret.NewSecurityContext("123456789", config.SecretStore)
|
|
||||||
otherCtx := security.NewContext(context.Background(), sc1)
|
otherCtx := security.NewContext(context.Background(), sc1)
|
||||||
|
|
||||||
sc2 := proxycachesecret.NewSecurityContext(context.Background(), "library/hello-world")
|
sc2 := proxycachesecret.NewSecurityContext("library/hello-world")
|
||||||
proxyCtx := security.NewContext(context.Background(), sc2)
|
proxyCtx := security.NewContext(context.Background(), sc2)
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -93,7 +93,7 @@ func (a *authProxy) Generate(req *http.Request) security.Context {
|
|||||||
}
|
}
|
||||||
user.GroupIDs = u2.GroupIDs
|
user.GroupIDs = u2.GroupIDs
|
||||||
log.Debugf("an auth proxy security context generated for request %s %s", req.Method, req.URL.Path)
|
log.Debugf("an auth proxy security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return local.NewSecurityContext(user, config.GlobalProjectMgr)
|
return local.NewSecurityContext(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authProxy) matchAuthProxyUserName(name string) (string, bool) {
|
func (a *authProxy) matchAuthProxyUserName(name string) (string, bool) {
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
"github.com/goharbor/harbor/src/common/security/local"
|
"github.com/goharbor/harbor/src/common/security/local"
|
||||||
"github.com/goharbor/harbor/src/core/auth"
|
"github.com/goharbor/harbor/src/core/auth"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -46,5 +45,5 @@ func (b *basicAuth) Generate(req *http.Request) security.Context {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debugf("a basic auth security context generated for request %s %s", req.Method, req.URL.Path)
|
log.Debugf("a basic auth security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return local.NewSecurityContext(user, config.GlobalProjectMgr)
|
return local.NewSecurityContext(user)
|
||||||
}
|
}
|
||||||
|
@ -66,5 +66,5 @@ func (i *idToken) Generate(req *http.Request) security.Context {
|
|||||||
}
|
}
|
||||||
oidc.InjectGroupsToUser(info, u)
|
oidc.InjectGroupsToUser(info, u)
|
||||||
log.Debugf("an ID token security context generated for request %s %s", req.Method, req.URL.Path)
|
log.Debugf("an ID token security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return local.NewSecurityContext(u, config.GlobalProjectMgr)
|
return local.NewSecurityContext(u)
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,6 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
"github.com/goharbor/harbor/src/common/security/local"
|
"github.com/goharbor/harbor/src/common/security/local"
|
||||||
"github.com/goharbor/harbor/src/common/utils/oidc"
|
"github.com/goharbor/harbor/src/common/utils/oidc"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
|
||||||
"github.com/goharbor/harbor/src/lib"
|
"github.com/goharbor/harbor/src/lib"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
)
|
)
|
||||||
@ -61,7 +60,7 @@ func (o *oidcCli) Generate(req *http.Request) security.Context {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
logger.Debugf("an OIDC CLI security context generated for request %s %s", req.Method, req.URL.Path)
|
logger.Debugf("an OIDC CLI security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return local.NewSecurityContext(user, config.GlobalProjectMgr)
|
return local.NewSecurityContext(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *oidcCli) valid(req *http.Request) bool {
|
func (o *oidcCli) valid(req *http.Request) bool {
|
||||||
|
@ -38,5 +38,5 @@ func (p *proxyCacheSecret) Generate(req *http.Request) security.Context {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debugf("a proxy cache secret security context generated for request %s %s", req.Method, req.URL.Path)
|
log.Debugf("a proxy cache secret security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return proxycachesecret.NewSecurityContext(req.Context(), artifact.Repository)
|
return proxycachesecret.NewSecurityContext(artifact.Repository)
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
pkgrobot "github.com/goharbor/harbor/src/pkg/robot"
|
pkgrobot "github.com/goharbor/harbor/src/pkg/robot"
|
||||||
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
||||||
@ -66,5 +65,5 @@ func (r *robot) Generate(req *http.Request) security.Context {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debugf("a robot security context generated for request %s %s", req.Method, req.URL.Path)
|
log.Debugf("a robot security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return robotCtx.NewSecurityContext(robot, config.GlobalProjectMgr, rtk.Claims.(*robot_claim.Claim).Access)
|
return robotCtx.NewSecurityContext(robot, rtk.Claims.(*robot_claim.Claim).Access)
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,6 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/models"
|
"github.com/goharbor/harbor/src/common/models"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
"github.com/goharbor/harbor/src/common/security/local"
|
"github.com/goharbor/harbor/src/common/security/local"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,5 +44,5 @@ func (s *session) Generate(req *http.Request) security.Context {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debugf("a session security context generated for request %s %s", req.Method, req.URL.Path)
|
log.Debugf("a session security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return local.NewSecurityContext(&user, config.GlobalProjectMgr)
|
return local.NewSecurityContext(&user)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
"github.com/goharbor/harbor/src/common/security/local"
|
"github.com/goharbor/harbor/src/common/security/local"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,5 +26,5 @@ type unauthorized struct{}
|
|||||||
|
|
||||||
func (u *unauthorized) Generate(req *http.Request) security.Context {
|
func (u *unauthorized) Generate(req *http.Request) security.Context {
|
||||||
log.G(req.Context()).Debugf("an unauthorized security context generated for request %s %s", req.Method, req.URL.Path)
|
log.G(req.Context()).Debugf("an unauthorized security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return local.NewSecurityContext(nil, config.GlobalProjectMgr)
|
return local.NewSecurityContext(nil)
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ func SkipPolicyChecking(ctx context.Context, projectID int64) bool {
|
|||||||
|
|
||||||
// only scanner pull access can bypass.
|
// only scanner pull access can bypass.
|
||||||
if ok && secCtx.Name() == "v2token" &&
|
if ok && secCtx.Name() == "v2token" &&
|
||||||
secCtx.Can(rbac.ActionScannerPull, rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceRepository)) {
|
secCtx.Can(ctx, rbac.ActionScannerPull, rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceRepository)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,8 +16,6 @@ package v2auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/goharbor/harbor/src/lib"
|
|
||||||
lib_http "github.com/goharbor/harbor/src/lib/http"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
@ -28,7 +26,9 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
"github.com/goharbor/harbor/src/core/promgr"
|
"github.com/goharbor/harbor/src/core/promgr"
|
||||||
"github.com/goharbor/harbor/src/core/service/token"
|
"github.com/goharbor/harbor/src/core/service/token"
|
||||||
|
"github.com/goharbor/harbor/src/lib"
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
|
lib_http "github.com/goharbor/harbor/src/lib/http"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ func (rc *reqChecker) check(req *http.Request) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
resource := rbac.NewProjectNamespace(pid).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(pid).Resource(rbac.ResourceRepository)
|
||||||
if !securityCtx.Can(a.action, resource) {
|
if !securityCtx.Can(req.Context(), a.action, resource) {
|
||||||
return getChallenge(req, al), fmt.Errorf("unauthorized to access repository: %s, action: %s", a.name, a.action)
|
return getChallenge(req, al), fmt.Errorf("unauthorized to access repository: %s, action: %s", a.name, a.action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +108,7 @@ func TestMiddleware(t *testing.T) {
|
|||||||
sc := &securitytesting.Context{}
|
sc := &securitytesting.Context{}
|
||||||
sc.On("IsAuthenticated").Return(true)
|
sc.On("IsAuthenticated").Return(true)
|
||||||
sc.On("IsSysAdmin").Return(false)
|
sc.On("IsSysAdmin").Return(false)
|
||||||
mock.OnAnything(sc, "Can").Return(func(action types.Action, resource types.Resource) bool {
|
mock.OnAnything(sc, "Can").Return(func(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
perms := map[string]map[rbac.Action]struct{}{
|
perms := map[string]map[rbac.Action]struct{}{
|
||||||
"/project/1/repository": {
|
"/project/1/repository": {
|
||||||
rbac.ActionPull: {},
|
rbac.ActionPull: {},
|
||||||
|
@ -57,7 +57,7 @@ func (*BaseAPI) HasPermission(ctx context.Context, action rbac.Action, resource
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Can(action, resource)
|
return s.Can(ctx, action, resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HasProjectPermission returns true when the request has action permission on project subresource
|
// HasProjectPermission returns true when the request has action permission on project subresource
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
package security
|
package security
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
context "context"
|
||||||
|
|
||||||
mock "github.com/stretchr/testify/mock"
|
mock "github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
types "github.com/goharbor/harbor/src/pkg/permission/types"
|
types "github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
@ -13,13 +15,13 @@ type Context struct {
|
|||||||
mock.Mock
|
mock.Mock
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can provides a mock function with given fields: action, resource
|
// Can provides a mock function with given fields: ctx, action, resource
|
||||||
func (_m *Context) Can(action types.Action, resource types.Resource) bool {
|
func (_m *Context) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
ret := _m.Called(action, resource)
|
ret := _m.Called(ctx, action, resource)
|
||||||
|
|
||||||
var r0 bool
|
var r0 bool
|
||||||
if rf, ok := ret.Get(0).(func(types.Action, types.Resource) bool); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, types.Action, types.Resource) bool); ok {
|
||||||
r0 = rf(action, resource)
|
r0 = rf(ctx, action, resource)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(bool)
|
r0 = ret.Get(0).(bool)
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,29 @@ func (_m *Controller) List(ctx context.Context, query *q.Query, options ...proje
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListRoles provides a mock function with given fields: ctx, projectID, u
|
||||||
|
func (_m *Controller) ListRoles(ctx context.Context, projectID int64, u *models.User) ([]int, error) {
|
||||||
|
ret := _m.Called(ctx, projectID, u)
|
||||||
|
|
||||||
|
var r0 []int
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int64, *models.User) []int); ok {
|
||||||
|
r0 = rf(ctx, projectID, u)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int64, *models.User) error); ok {
|
||||||
|
r1 = rf(ctx, projectID, u)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// Update provides a mock function with given fields: ctx, _a1
|
// Update provides a mock function with given fields: ctx, _a1
|
||||||
func (_m *Controller) Update(ctx context.Context, _a1 *models.Project) error {
|
func (_m *Controller) Update(ctx context.Context, _a1 *models.Project) error {
|
||||||
ret := _m.Called(ctx, _a1)
|
ret := _m.Called(ctx, _a1)
|
||||||
|
@ -117,3 +117,33 @@ func (_m *Manager) List(ctx context.Context, query *q.Query) ([]*models.Project,
|
|||||||
|
|
||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListRoles provides a mock function with given fields: ctx, projectID, userID, groupIDs
|
||||||
|
func (_m *Manager) ListRoles(ctx context.Context, projectID int64, userID int, groupIDs ...int) ([]int, error) {
|
||||||
|
_va := make([]interface{}, len(groupIDs))
|
||||||
|
for _i := range groupIDs {
|
||||||
|
_va[_i] = groupIDs[_i]
|
||||||
|
}
|
||||||
|
var _ca []interface{}
|
||||||
|
_ca = append(_ca, ctx, projectID, userID)
|
||||||
|
_ca = append(_ca, _va...)
|
||||||
|
ret := _m.Called(_ca...)
|
||||||
|
|
||||||
|
var r0 []int
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int64, int, ...int) []int); ok {
|
||||||
|
r0 = rf(ctx, projectID, userID, groupIDs...)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int64, int, ...int) error); ok {
|
||||||
|
r1 = rf(ctx, projectID, userID, groupIDs...)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user