fix(rbac): NewProjectNamespace in rbac only accept projectID

Closes #8635

Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
He Weiwei 2019-08-21 19:07:19 +00:00
parent 427a369d3a
commit 8effdc6f18
27 changed files with 170 additions and 241 deletions

View File

@ -31,8 +31,8 @@ type Namespace interface {
}
type projectNamespace struct {
projectIDOrName interface{}
isPublic bool
projectID int64
isPublic bool
}
func (ns *projectNamespace) Kind() string {
@ -40,11 +40,11 @@ func (ns *projectNamespace) Kind() string {
}
func (ns *projectNamespace) Resource(subresources ...Resource) Resource {
return Resource(fmt.Sprintf("/project/%v", ns.projectIDOrName)).Subresource(subresources...)
return Resource(fmt.Sprintf("/project/%d", ns.projectID)).Subresource(subresources...)
}
func (ns *projectNamespace) Identity() interface{} {
return ns.projectIDOrName
return ns.projectID
}
func (ns *projectNamespace) IsPublic() bool {
@ -52,10 +52,10 @@ func (ns *projectNamespace) IsPublic() bool {
}
// NewProjectNamespace returns namespace for project
func NewProjectNamespace(projectIDOrName interface{}, isPublic ...bool) Namespace {
func NewProjectNamespace(projectID int64, isPublic ...bool) Namespace {
isPublicNamespace := false
if len(isPublic) > 0 {
isPublicNamespace = isPublic[0]
}
return &projectNamespace{projectIDOrName: projectIDOrName, isPublic: isPublicNamespace}
return &projectNamespace{projectID: projectID, isPublic: isPublicNamespace}
}

View File

@ -27,7 +27,7 @@ type ProjectNamespaceTestSuite struct {
func (suite *ProjectNamespaceTestSuite) TestResource() {
var namespace Namespace
namespace = &projectNamespace{projectIDOrName: int64(1)}
namespace = &projectNamespace{projectID: int64(1)}
suite.Equal(namespace.Resource(Resource("image")), Resource("/project/1/image"))
}
@ -35,9 +35,6 @@ func (suite *ProjectNamespaceTestSuite) TestResource() {
func (suite *ProjectNamespaceTestSuite) TestIdentity() {
namespace, _ := Resource("/project/1/image").GetNamespace()
suite.Equal(namespace.Identity(), int64(1))
namespace, _ = Resource("/project/library/image").GetNamespace()
suite.Equal(namespace.Identity(), "library")
}
func TestProjectNamespaceTestSuite(t *testing.T) {

View File

@ -37,14 +37,10 @@ func projectNamespaceParser(resource Resource) (Namespace, error) {
return nil, errors.New("not support resource")
}
var projectIDOrName interface{}
id, err := strconv.ParseInt(matches[1], 10, 64)
if err == nil {
projectIDOrName = id
} else {
projectIDOrName = matches[1]
projectID, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return nil, err
}
return &projectNamespace{projectIDOrName: projectIDOrName}, nil
return &projectNamespace{projectID: projectID}, nil
}

View File

@ -26,7 +26,7 @@ type ProjectParserTestSuite struct {
func (suite *ProjectParserTestSuite) TestParse() {
namespace, err := projectNamespaceParser(Resource("/project/1/image"))
suite.Equal(namespace, &projectNamespace{projectIDOrName: int64(1)})
suite.Equal(namespace, &projectNamespace{projectID: 1})
suite.Nil(err)
namespace, err = projectNamespaceParser(Resource("/fake/1/image"))

View File

@ -50,8 +50,8 @@ type VisitorTestSuite struct {
}
func (suite *VisitorTestSuite) TestGetPolicies() {
namespace := rbac.NewProjectNamespace("library", false)
publicNamespace := rbac.NewProjectNamespace("library", true)
namespace := rbac.NewProjectNamespace(1, false)
publicNamespace := rbac.NewProjectNamespace(1, true)
anonymous := NewUser(anonymousCtx, namespace)
suite.Nil(anonymous.GetPolicies())
@ -73,7 +73,7 @@ func (suite *VisitorTestSuite) TestGetPolicies() {
}
func (suite *VisitorTestSuite) TestGetRoles() {
namespace := rbac.NewProjectNamespace("library", false)
namespace := rbac.NewProjectNamespace(1, false)
anonymous := NewUser(anonymousCtx, namespace)
suite.Nil(anonymous.GetRoles())

View File

@ -75,10 +75,10 @@ func (s *SecurityContext) Can(action rbac.Action, resource rbac.Resource) bool {
if err == nil {
switch ns.Kind() {
case "project":
projectIDOrName := ns.Identity()
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
projectNamespace := rbac.NewProjectNamespace(projectIDOrName, isPublicProject)
user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectIDOrName)...)
projectID := ns.Identity().(int64)
isPublicProject, _ := s.pm.IsPublic(projectID)
projectNamespace := rbac.NewProjectNamespace(projectID, isPublicProject)
user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectID)...)
return rbac.HasPermission(user, resource, action)
}
}

View File

@ -72,10 +72,10 @@ func (s *SecurityContext) Can(action rbac.Action, resource rbac.Resource) bool {
if err == nil {
switch ns.Kind() {
case "project":
projectIDOrName := ns.Identity()
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
projectNamespace := rbac.NewProjectNamespace(projectIDOrName, isPublicProject)
user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectIDOrName)...)
projectID := ns.Identity().(int64)
isPublicProject, _ := s.pm.IsPublic(projectID)
projectNamespace := rbac.NewProjectNamespace(projectID, isPublicProject)
user := project.NewUser(s, projectNamespace, s.GetProjectRoles(projectID)...)
return rbac.HasPermission(user, resource, action)
}
}

View File

@ -176,12 +176,12 @@ func TestHasPullPerm(t *testing.T) {
// public project
ctx := NewSecurityContext(nil, pm)
resource := rbac.NewProjectNamespace("library").Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(1).Resource(rbac.ResourceRepository)
assert.True(t, ctx.Can(rbac.ActionPull, resource))
// private project, unauthenticated
ctx = NewSecurityContext(nil, pm)
resource = rbac.NewProjectNamespace(private.Name).Resource(rbac.ResourceRepository)
resource = rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
assert.False(t, ctx.Can(rbac.ActionPull, resource))
// private project, authenticated, has no perm
@ -203,7 +203,7 @@ func TestHasPullPerm(t *testing.T) {
}
func TestHasPushPerm(t *testing.T) {
resource := rbac.NewProjectNamespace(private.Name).Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
// unauthenticated
ctx := NewSecurityContext(nil, pm)
@ -226,7 +226,7 @@ func TestHasPushPerm(t *testing.T) {
}
func TestHasPushPullPerm(t *testing.T) {
resource := rbac.NewProjectNamespace(private.Name).Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
// unauthenticated
ctx := NewSecurityContext(nil, pm)
@ -265,7 +265,7 @@ func TestHasPushPullPermWithGroup(t *testing.T) {
developer.GroupIDs = []int{userGroups[0].ID}
resource := rbac.NewProjectNamespace(project.Name).Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
ctx := NewSecurityContext(developer, pm)
assert.True(t, ctx.Can(rbac.ActionPush, resource))

View File

@ -76,9 +76,9 @@ func (s *SecurityContext) Can(action rbac.Action, resource rbac.Resource) bool {
if err == nil {
switch ns.Kind() {
case "project":
projectIDOrName := ns.Identity()
isPublicProject, _ := s.pm.IsPublic(projectIDOrName)
projectNamespace := rbac.NewProjectNamespace(projectIDOrName, isPublicProject)
projectID := ns.Identity().(int64)
isPublicProject, _ := s.pm.IsPublic(projectID)
projectNamespace := rbac.NewProjectNamespace(projectID, isPublicProject)
robot := NewRobot(s.GetUsername(), projectNamespace, s.policy)
return rbac.HasPermission(robot, resource, action)
}

View File

@ -15,6 +15,7 @@
package robot
import (
"fmt"
"os"
"strconv"
"testing"
@ -136,7 +137,7 @@ func TestIsSolutionUser(t *testing.T) {
func TestHasPullPerm(t *testing.T) {
policies := []*rbac.Policy{
{
Resource: "/project/testrobot/repository",
Resource: rbac.Resource(fmt.Sprintf("/project/%d/repository", private.ProjectID)),
Action: rbac.ActionPull,
},
}
@ -146,14 +147,14 @@ func TestHasPullPerm(t *testing.T) {
}
ctx := NewSecurityContext(robot, pm, policies)
resource := rbac.NewProjectNamespace(private.Name).Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
assert.True(t, ctx.Can(rbac.ActionPull, resource))
}
func TestHasPushPerm(t *testing.T) {
policies := []*rbac.Policy{
{
Resource: "/project/testrobot/repository",
Resource: rbac.Resource(fmt.Sprintf("/project/%d/repository", private.ProjectID)),
Action: rbac.ActionPush,
},
}
@ -163,18 +164,18 @@ func TestHasPushPerm(t *testing.T) {
}
ctx := NewSecurityContext(robot, pm, policies)
resource := rbac.NewProjectNamespace(private.Name).Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
assert.True(t, ctx.Can(rbac.ActionPush, resource))
}
func TestHasPushPullPerm(t *testing.T) {
policies := []*rbac.Policy{
{
Resource: "/project/testrobot/repository",
Resource: rbac.Resource(fmt.Sprintf("/project/%d/repository", private.ProjectID)),
Action: rbac.ActionPush,
},
{
Resource: "/project/testrobot/repository",
Resource: rbac.Resource(fmt.Sprintf("/project/%d/repository", private.ProjectID)),
Action: rbac.ActionPull,
},
}
@ -184,7 +185,7 @@ func TestHasPushPullPerm(t *testing.T) {
}
ctx := NewSecurityContext(robot, pm, policies)
resource := rbac.NewProjectNamespace(private.Name).Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
assert.True(t, ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource))
}

View File

@ -1,9 +1,24 @@
// 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/stretchr/testify/assert"
"testing"
)
func TestGetPolicies(t *testing.T) {
@ -17,7 +32,7 @@ func TestGetPolicies(t *testing.T) {
robot := robot{
username: "test",
namespace: rbac.NewProjectNamespace("library", false),
namespace: rbac.NewProjectNamespace(1, false),
policy: policies,
}

View File

@ -18,21 +18,21 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/goharbor/harbor/src/pkg/retention"
"github.com/goharbor/harbor/src/pkg/scheduler"
"net/http"
"github.com/ghodss/yaml"
"github.com/goharbor/harbor/src/common/api"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/security"
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
"github.com/goharbor/harbor/src/core/filter"
"github.com/goharbor/harbor/src/core/promgr"
"github.com/goharbor/harbor/src/pkg/project"
"github.com/goharbor/harbor/src/pkg/repository"
"github.com/goharbor/harbor/src/pkg/retention"
"github.com/goharbor/harbor/src/pkg/scheduler"
)
const (
@ -49,6 +49,10 @@ var (
retentionController retention.APIController
)
var (
errNotFound = errors.New("not found")
)
// BaseController ...
type BaseController struct {
api.BaseAPI
@ -79,6 +83,71 @@ func (b *BaseController) Prepare() {
b.ProjectMgr = pm
}
// RequireAuthenticated returns true when the request is authenticated
// otherwise send Unauthorized response and returns false
func (b *BaseController) RequireAuthenticated() bool {
if !b.SecurityCtx.IsAuthenticated() {
b.SendUnAuthorizedError(errors.New("Unauthorized"))
return false
}
return true
}
// HasProjectPermission returns true when the request has action permission on project subresource
func (b *BaseController) HasProjectPermission(projectIDOrName interface{}, action rbac.Action, subresource ...rbac.Resource) (bool, error) {
projectID, projectName, err := utils.ParseProjectIDOrName(projectIDOrName)
if err != nil {
return false, err
}
if projectName != "" {
project, err := b.ProjectMgr.Get(projectName)
if err != nil {
return false, err
}
if project == nil {
return false, errNotFound
}
projectID = project.ProjectID
}
resource := rbac.NewProjectNamespace(projectID).Resource(subresource...)
if !b.SecurityCtx.Can(action, resource) {
return false, nil
}
return true, nil
}
// RequireProjectAccess returns true when the request has action access on project subresource
// otherwise send UnAuthorized or Forbidden response and returns false
func (b *BaseController) RequireProjectAccess(projectIDOrName interface{}, action rbac.Action, subresource ...rbac.Resource) bool {
hasPermission, err := b.HasProjectPermission(projectIDOrName, action, subresource...)
if err != nil {
if err == errNotFound {
b.SendNotFoundError(fmt.Errorf("project %v not found", projectIDOrName))
} else {
b.SendInternalServerError(err)
}
return false
}
if !hasPermission {
if !b.SecurityCtx.IsAuthenticated() {
b.SendUnAuthorizedError(errors.New("UnAuthorized"))
} else {
b.SendForbiddenError(errors.New(b.SecurityCtx.GetUsername()))
}
return false
}
return true
}
// WriteJSONData writes the JSON data to the client.
func (b *BaseController) WriteJSONData(object interface{}) {
b.Data["json"] = object

View File

@ -58,14 +58,7 @@ func (cla *ChartLabelAPI) Prepare() {
}
func (cla *ChartLabelAPI) requireAccess(action rbac.Action) bool {
resource := rbac.NewProjectNamespace(cla.project.ProjectID).Resource(rbac.ResourceHelmChartVersionLabel)
if !cla.SecurityCtx.Can(action, resource) {
cla.SendForbiddenError(errors.New(cla.SecurityCtx.GetUsername()))
return false
}
return true
return cla.RequireProjectAccess(cla.project.ProjectID, action, rbac.ResourceHelmChartVersionLabel)
}
// MarkLabel handles the request of marking label to chart.

View File

@ -98,19 +98,8 @@ func (cra *ChartRepositoryAPI) requireAccess(action rbac.Action, subresource ...
if len(subresource) == 0 {
subresource = append(subresource, rbac.ResourceHelmChart)
}
resource := rbac.NewProjectNamespace(cra.namespace).Resource(subresource...)
if !cra.SecurityCtx.Can(action, resource) {
if !cra.SecurityCtx.IsAuthenticated() {
cra.SendUnAuthorizedError(errors.New("Unauthorized"))
} else {
cra.SendForbiddenError(errors.New(cra.SecurityCtx.GetUsername()))
}
return false
}
return true
return cra.RequireProjectAccess(cra.namespace, action, subresource...)
}
// GetHealthStatus handles GET /api/chartrepo/health

View File

@ -78,8 +78,7 @@ func (l *LabelAPI) requireAccess(label *models.Label, action rbac.Action, subres
if len(subresources) == 0 {
subresources = append(subresources, rbac.ResourceLabel)
}
resource := rbac.NewProjectNamespace(label.ProjectID).Resource(subresources...)
hasPermission = l.SecurityCtx.Can(action, resource)
hasPermission, _ = l.HasProjectPermission(label.ProjectID, action, subresources...)
}
if !hasPermission {
@ -203,13 +202,7 @@ func (l *LabelAPI) List() {
return
}
resource := rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceLabel)
if !l.SecurityCtx.Can(rbac.ActionList, resource) {
if !l.SecurityCtx.IsAuthenticated() {
l.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
l.SendForbiddenError(errors.New(l.SecurityCtx.GetUsername()))
if !l.RequireProjectAccess(projectID, rbac.ActionList, rbac.ResourceLabel) {
return
}
query.ProjectID = projectID

View File

@ -22,6 +22,7 @@ import (
"strings"
"errors"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/utils/log"
@ -90,18 +91,7 @@ func (m *MetadataAPI) Prepare() {
}
func (m *MetadataAPI) requireAccess(action rbac.Action) bool {
resource := rbac.NewProjectNamespace(m.project.ProjectID).Resource(rbac.ResourceMetadata)
if !m.SecurityCtx.Can(action, resource) {
if !m.SecurityCtx.IsAuthenticated() {
m.SendUnAuthorizedError(errors.New("Unauthorized"))
} else {
m.SendForbiddenError(errors.New(m.SecurityCtx.GetUsername()))
}
return false
}
return true
return m.RequireProjectAccess(m.project.ProjectID, action, rbac.ResourceMetadata)
}
// Get ...

View File

@ -93,16 +93,5 @@ func (w *NotificationJobAPI) validateRBAC(action rbac.Action, projectID int64) b
return true
}
project, err := w.ProjectMgr.Get(projectID)
if err != nil {
w.ParseAndHandleError(fmt.Sprintf("failed to get project %d", projectID), err)
return false
}
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceNotificationPolicy)
if !w.SecurityCtx.Can(action, resource) {
w.SendForbiddenError(errors.New(w.SecurityCtx.GetUsername()))
return false
}
return true
return w.RequireProjectAccess(projectID, action, rbac.ResourceNotificationPolicy)
}

View File

@ -283,18 +283,7 @@ func (w *NotificationPolicyAPI) validateRBAC(action rbac.Action, projectID int64
return true
}
project, err := w.ProjectMgr.Get(projectID)
if err != nil {
w.ParseAndHandleError(fmt.Sprintf("failed to get project %d", projectID), err)
return false
}
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceNotificationPolicy)
if !w.SecurityCtx.Can(action, resource) {
w.SendForbiddenError(errors.New(w.SecurityCtx.GetUsername()))
return false
}
return true
return w.RequireProjectAccess(projectID, action, rbac.ResourceNotificationPolicy)
}
func (w *NotificationPolicyAPI) validateTargets(policy *models.NotificationPolicy) bool {

View File

@ -86,20 +86,8 @@ func (p *ProjectAPI) requireAccess(action rbac.Action, subresource ...rbac.Resou
if len(subresource) == 0 {
subresource = append(subresource, rbac.ResourceSelf)
}
resource := rbac.NewProjectNamespace(p.project.ProjectID).Resource(subresource...)
if !p.SecurityCtx.Can(action, resource) {
if !p.SecurityCtx.IsAuthenticated() {
p.SendUnAuthorizedError(errors.New("Unauthorized"))
} else {
p.SendForbiddenError(errors.New(p.SecurityCtx.GetUsername()))
}
return false
}
return true
return p.RequireProjectAccess(p.project.ProjectID, action, subresource...)
}
// Post ...

View File

@ -99,19 +99,7 @@ func (pma *ProjectMemberAPI) Prepare() {
}
func (pma *ProjectMemberAPI) requireAccess(action rbac.Action) bool {
resource := rbac.NewProjectNamespace(pma.project.ProjectID).Resource(rbac.ResourceMember)
if !pma.SecurityCtx.Can(action, resource) {
if !pma.SecurityCtx.IsAuthenticated() {
pma.SendUnAuthorizedError(errors.New("Unauthorized"))
} else {
pma.SendForbiddenError(errors.New(pma.SecurityCtx.GetUsername()))
}
return false
}
return true
return pma.RequireProjectAccess(pma.project.ProjectID, action, rbac.ResourceMember)
}
// Get ...

View File

@ -111,13 +111,7 @@ func (ra *RepositoryAPI) Get() {
return
}
resource := rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceRepository)
if !ra.SecurityCtx.Can(rbac.ActionList, resource) {
if !ra.SecurityCtx.IsAuthenticated() {
ra.SendUnAuthorizedError(errors.New("Unauthorized"))
return
}
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
if !ra.RequireProjectAccess(projectID, rbac.ActionList, rbac.ResourceRepository) {
return
}
@ -228,14 +222,8 @@ func (ra *RepositoryAPI) Delete() {
return
}
if !ra.SecurityCtx.IsAuthenticated() {
ra.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
if !ra.SecurityCtx.Can(rbac.ActionDelete, resource) {
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
if !ra.RequireAuthenticated() ||
!ra.RequireProjectAccess(project.ProjectID, rbac.ActionDelete, rbac.ResourceRepository) {
return
}
@ -402,14 +390,9 @@ func (ra *RepositoryAPI) GetTag() {
ra.SendNotFoundError(fmt.Errorf("resource: %s:%s not found", repository, tag))
return
}
project, _ := utils.ParseRepository(repository)
resource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepositoryTag)
if !ra.SecurityCtx.Can(rbac.ActionRead, resource) {
if !ra.SecurityCtx.IsAuthenticated() {
ra.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
projectName, _ := utils.ParseRepository(repository)
if !ra.RequireProjectAccess(projectName, rbac.ActionRead, rbac.ResourceRepositoryTag) {
return
}
@ -502,16 +485,14 @@ func (ra *RepositoryAPI) Retag() {
}
// Check whether user has read permission to source project
srcResource := rbac.NewProjectNamespace(srcImage.Project).Resource(rbac.ResourceRepository)
if !ra.SecurityCtx.Can(rbac.ActionPull, srcResource) {
if hasPermission, _ := ra.HasProjectPermission(srcImage.Project, rbac.ActionPull, rbac.ResourceRepository); !hasPermission {
log.Errorf("user has no read permission to project '%s'", srcImage.Project)
ra.SendForbiddenError(fmt.Errorf("%s has no read permission to project %s", ra.SecurityCtx.GetUsername(), srcImage.Project))
return
}
// Check whether user has write permission to target project
destResource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepository)
if !ra.SecurityCtx.Can(rbac.ActionPush, destResource) {
if hasPermission, _ := ra.HasProjectPermission(project, rbac.ActionPush, rbac.ResourceRepository); !hasPermission {
log.Errorf("user has no write permission to project '%s'", project)
ra.SendForbiddenError(fmt.Errorf("%s has no write permission to project %s", ra.SecurityCtx.GetUsername(), project))
return
@ -549,13 +530,7 @@ func (ra *RepositoryAPI) GetTags() {
return
}
resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTag)
if !ra.SecurityCtx.Can(rbac.ActionList, resource) {
if !ra.SecurityCtx.IsAuthenticated() {
ra.SendUnAuthorizedError(errors.New("UnAuthorized"))
return
}
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
if !ra.RequireProjectAccess(projectName, rbac.ActionList, rbac.ResourceRepositoryTag) {
return
}
@ -815,14 +790,7 @@ func (ra *RepositoryAPI) GetManifests() {
return
}
resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTagManifest)
if !ra.SecurityCtx.Can(rbac.ActionRead, resource) {
if !ra.SecurityCtx.IsAuthenticated() {
ra.SendUnAuthorizedError(errors.New("Unauthorized"))
return
}
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
if !ra.RequireProjectAccess(projectName, rbac.ActionRead, rbac.ResourceRepositoryTagManifest) {
return
}
@ -943,10 +911,8 @@ func (ra *RepositoryAPI) Put() {
return
}
project, _ := utils.ParseRepository(name)
resource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepository)
if !ra.SecurityCtx.Can(rbac.ActionUpdate, resource) {
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
projectName, _ := utils.ParseRepository(name)
if !ra.RequireProjectAccess(projectName, rbac.ActionUpdate, rbac.ResourceRepository) {
return
}
@ -982,13 +948,7 @@ func (ra *RepositoryAPI) GetSignatures() {
return
}
resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepository)
if !ra.SecurityCtx.Can(rbac.ActionRead, resource) {
if !ra.SecurityCtx.IsAuthenticated() {
ra.SendUnAuthorizedError(errors.New("Unauthorized"))
return
}
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
if !ra.RequireProjectAccess(projectName, rbac.ActionRead, rbac.ResourceRepository) {
return
}
@ -1028,9 +988,7 @@ func (ra *RepositoryAPI) ScanImage() {
return
}
resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTagScanJob)
if !ra.SecurityCtx.Can(rbac.ActionCreate, resource) {
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
if !ra.RequireProjectAccess(projectName, rbac.ActionCreate, rbac.ResourceRepositoryTagScanJob) {
return
}
err = coreutils.TriggerImageScan(repoName, tag)
@ -1059,15 +1017,9 @@ func (ra *RepositoryAPI) VulnerabilityDetails() {
ra.SendNotFoundError(fmt.Errorf("resource: %s:%s not found", repository, tag))
return
}
project, _ := utils.ParseRepository(repository)
resource := rbac.NewProjectNamespace(project).Resource(rbac.ResourceRepositoryTagVulnerability)
if !ra.SecurityCtx.Can(rbac.ActionList, resource) {
if !ra.SecurityCtx.IsAuthenticated() {
ra.SendUnAuthorizedError(errors.New("Unauthorized"))
return
}
ra.SendForbiddenError(errors.New(ra.SecurityCtx.GetUsername()))
projectName, _ := utils.ParseRepository(repository)
if !ra.RequireProjectAccess(projectName, rbac.ActionList, rbac.ResourceRepositoryTagVulnerability) {
return
}
res, err := scan.VulnListByDigest(digest)

View File

@ -91,19 +91,8 @@ func (r *RepositoryLabelAPI) requireAccess(action rbac.Action, subresource ...rb
if len(subresource) == 0 {
subresource = append(subresource, rbac.ResourceRepositoryLabel)
}
resource := rbac.NewProjectNamespace(r.repository.ProjectID).Resource(rbac.ResourceRepositoryLabel)
if !r.SecurityCtx.Can(action, resource) {
if !r.SecurityCtx.IsAuthenticated() {
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
} else {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
}
return false
}
return true
return r.RequireProjectAccess(r.repository.ProjectID, action, subresource...)
}
func (r *RepositoryLabelAPI) isValidLabelReq() bool {

View File

@ -418,8 +418,7 @@ func (r *RetentionAPI) requireAccess(p *policy.Metadata, action rbac.Action, sub
if len(subresources) == 0 {
subresources = append(subresources, rbac.ResourceTagRetention)
}
resource := rbac.NewProjectNamespace(p.Scope.Reference).Resource(subresources...)
hasPermission = r.SecurityCtx.Can(action, resource)
hasPermission, _ = r.HasProjectPermission(p.Scope.Reference, action, subresources...)
default:
hasPermission = r.SecurityCtx.IsSysAdmin()
}

View File

@ -17,16 +17,16 @@ package api
import (
"errors"
"fmt"
"net/http"
"strconv"
"time"
"github.com/goharbor/harbor/src/common"
"github.com/goharbor/harbor/src/common/dao"
"github.com/goharbor/harbor/src/common/models"
"github.com/goharbor/harbor/src/common/rbac"
"github.com/goharbor/harbor/src/common/token"
"net/http"
"strconv"
"github.com/goharbor/harbor/src/core/config"
"time"
)
// RobotAPI ...
@ -91,13 +91,7 @@ func (r *RobotAPI) Prepare() {
}
func (r *RobotAPI) requireAccess(action rbac.Action) bool {
resource := rbac.NewProjectNamespace(r.project.ProjectID).Resource(rbac.ResourceRobot)
if !r.SecurityCtx.Can(action, resource) {
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
return false
}
return true
return r.RequireProjectAccess(r.project.ProjectID, action, rbac.ResourceRobot)
}
// Post ...

View File

@ -55,12 +55,10 @@ func (sj *ScanJobAPI) Prepare() {
sj.SendInternalServerError(errors.New("Failed to get Job data"))
return
}
projectName := strings.SplitN(data.Repository, "/", 2)[0]
resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepositoryTagScanJob)
if !sj.SecurityCtx.Can(rbac.ActionRead, resource) {
projectName := strings.SplitN(data.Repository, "/", 2)[0]
if !sj.RequireProjectAccess(projectName, rbac.ActionRead, rbac.ResourceRepositoryTagScanJob) {
log.Errorf("User does not have read permission for project: %s", projectName)
sj.SendForbiddenError(errors.New(sj.SecurityCtx.GetUsername()))
return
}
sj.projectName = projectName

View File

@ -612,7 +612,7 @@ func TestUsersCurrentPermissions(t *testing.T) {
assert := assert.New(t)
apiTest := newHarborAPI()
httpStatusCode, permissions, err := apiTest.UsersGetPermissions("current", "/project/library", *projAdmin)
httpStatusCode, permissions, err := apiTest.UsersGetPermissions("current", "/project/1", *projAdmin)
assert.Nil(err)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.NotEmpty(permissions, "permissions should not be empty")
@ -622,11 +622,11 @@ func TestUsersCurrentPermissions(t *testing.T) {
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
assert.Empty(permissions, "permissions should be empty")
httpStatusCode, _, err = apiTest.UsersGetPermissions(projAdminID, "/project/library", *projAdmin)
httpStatusCode, _, err = apiTest.UsersGetPermissions(projAdminID, "/project/1", *projAdmin)
assert.Nil(err)
assert.Equal(int(200), httpStatusCode, "httpStatusCode should be 200")
httpStatusCode, _, err = apiTest.UsersGetPermissions(projDeveloperID, "/project/library", *projAdmin)
httpStatusCode, _, err = apiTest.UsersGetPermissions(projDeveloperID, "/project/1", *projAdmin)
assert.Nil(err)
assert.Equal(int(403), httpStatusCode, "httpStatusCode should be 403")
}

View File

@ -162,17 +162,17 @@ func (rep repositoryFilter) filter(ctx security.Context, pm promgr.ProjectManage
projectName := img.namespace
permission := ""
exist, err := pm.Exists(projectName)
project, err := pm.Get(projectName)
if err != nil {
return err
}
if !exist {
if project == nil {
log.Debugf("project %s does not exist, set empty permission", projectName)
a.Actions = []string{}
return nil
}
resource := rbac.NewProjectNamespace(projectName).Resource(rbac.ResourceRepository)
resource := rbac.NewProjectNamespace(project.ProjectID).Resource(rbac.ResourceRepository)
if ctx.Can(rbac.ActionPush, resource) && ctx.Can(rbac.ActionPull, resource) {
permission = "RWM"
} else if ctx.Can(rbac.ActionPush, resource) {