mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-23 09:08:26 +01:00
fix(rbac): NewProjectNamespace in rbac only accept projectID
Closes #8635 Signed-off-by: He Weiwei <hweiwei@vmware.com>
This commit is contained in:
parent
427a369d3a
commit
8effdc6f18
@ -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}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"))
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ...
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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 ...
|
||||
|
@ -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 ...
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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 ...
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user