Merge pull request #13306 from heww/refactor-security-context

refactor(security): use controller instead of promgr in security
This commit is contained in:
Daniel Jiang 2020-10-29 02:39:59 +08:00 committed by GitHub
commit 535728d11f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 744 additions and 643 deletions

View File

@ -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
} }
}) })
} }

View File

@ -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)
} }
}) })
} }

View File

@ -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,
}
}

View File

@ -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{}

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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

View File

@ -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) {

View File

@ -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
} }

View File

@ -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)
}
})
}
} }

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
} }

View File

@ -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))
} }

View File

@ -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)
},
} }
} }

View File

@ -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))
} }
} }

View File

@ -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 {

View File

@ -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
} }

View File

@ -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)
} }
} }

View File

@ -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)

View File

@ -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
} }

View File

@ -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")
} }

View File

@ -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

View File

@ -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
} }

View File

@ -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
} }
} }

View File

@ -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
} }

View File

@ -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

View File

@ -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

View File

@ -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)
}) })

View File

@ -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
}

View File

@ -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{})
} }

View File

@ -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...)
}

View File

@ -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()

View File

@ -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

View File

@ -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) {

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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 {

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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)
} }
} }

View File

@ -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: {},

View File

@ -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

View File

@ -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)
} }

View File

@ -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)

View File

@ -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
}