mirror of
https://github.com/goharbor/harbor.git
synced 2025-04-17 01:15:56 +02:00
enable system resource access (#13826)
1, introduce & define the system resources. 2, replace the IsSysAdmin judge method. 3, give the robot the system access capability. Signed-off-by: Wang Yan <wangyan@vmware.com>
This commit is contained in:
parent
be98748ca7
commit
0cf43d766c
@ -56,5 +56,20 @@ const (
|
||||
ResourceArtifactAddition = Resource("artifact-addition")
|
||||
ResourceArtifactLabel = Resource("artifact-label")
|
||||
ResourcePreatPolicy = Resource("preheat-policy")
|
||||
ResourcePreatInstance = Resource("preheat-instance")
|
||||
ResourceSelf = Resource("") // subresource for self
|
||||
|
||||
ResourceAuditLog = Resource("audit-log")
|
||||
ResourceCatalog = Resource("catalog")
|
||||
ResourceProject = Resource("project")
|
||||
ResourceUser = Resource("user")
|
||||
ResourceUserGroup = Resource("user-group")
|
||||
ResourceRegistry = Resource("registry")
|
||||
ResourceReplication = Resource("replication")
|
||||
ResourceDistribution = Resource("distribution")
|
||||
ResourceGarbageCollection = Resource("garbage-collection")
|
||||
ResourceReplicationAdapter = Resource("replication-adapter")
|
||||
ResourceReplicationPolicy = Resource("replication-policy")
|
||||
ResourceScanAll = Resource("scan-all")
|
||||
ResourceSystemVolumes = Resource("system-volumes")
|
||||
)
|
||||
|
19
src/common/rbac/system/evaluator.go
Normal file
19
src/common/rbac/system/evaluator.go
Normal file
@ -0,0 +1,19 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"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/rbac"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
)
|
||||
|
||||
// NewEvaluator create evaluator for the system
|
||||
func NewEvaluator(username string, policies []*types.Policy) evaluator.Evaluator {
|
||||
return namespace.New(NamespaceKind, func(ctx context.Context, ns types.Namespace) evaluator.Evaluator {
|
||||
return rbac.New(&rbacUser{
|
||||
username: username,
|
||||
policies: policies,
|
||||
})
|
||||
})
|
||||
}
|
50
src/common/rbac/system/namespace.go
Normal file
50
src/common/rbac/system/namespace.go
Normal file
@ -0,0 +1,50 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// NamespaceKind kind for system namespace
|
||||
NamespaceKind = "system"
|
||||
// NamespacePrefix for system namespace
|
||||
NamespacePrefix = "/system"
|
||||
)
|
||||
|
||||
type systemNamespace struct {
|
||||
}
|
||||
|
||||
func (ns *systemNamespace) Kind() string {
|
||||
return NamespaceKind
|
||||
}
|
||||
|
||||
func (ns *systemNamespace) Resource(subresources ...types.Resource) types.Resource {
|
||||
return types.Resource(fmt.Sprintf("/system/")).Subresource(subresources...)
|
||||
}
|
||||
|
||||
func (ns *systemNamespace) Identity() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *systemNamespace) GetPolicies() []*types.Policy {
|
||||
return policies
|
||||
}
|
||||
|
||||
// NewNamespace returns namespace for project
|
||||
func NewNamespace() types.Namespace {
|
||||
return &systemNamespace{}
|
||||
}
|
||||
|
||||
// NamespaceParse ...
|
||||
func NamespaceParse(resource types.Resource) (types.Namespace, bool) {
|
||||
if strings.HasPrefix(resource.String(), NamespacePrefix) {
|
||||
return NewNamespace(), true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func init() {
|
||||
types.RegistryNamespaceParse(NamespaceKind, NamespaceParse)
|
||||
}
|
64
src/common/rbac/system/policies.go
Normal file
64
src/common/rbac/system/policies.go
Normal file
@ -0,0 +1,64 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
)
|
||||
|
||||
var (
|
||||
policies = []*types.Policy{
|
||||
{Resource: rbac.ResourceCatalog, Action: rbac.ActionRead},
|
||||
|
||||
{Resource: rbac.ResourceAuditLog, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceProject, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceProject, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceProject, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceProject, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceProject, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceUser, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceUser, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceUser, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceUser, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceUser, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceUserGroup, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceRegistry, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceRegistry, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceRegistry, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceRegistry, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceRegistry, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionList},
|
||||
{Resource: rbac.ResourceReplication, Action: rbac.ActionDelete},
|
||||
|
||||
{Resource: rbac.ResourceDistribution, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceDistribution, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceDistribution, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceDistribution, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceDistribution, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceGarbageCollection, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceScanAll, Action: rbac.ActionCreate},
|
||||
{Resource: rbac.ResourceScanAll, Action: rbac.ActionRead},
|
||||
{Resource: rbac.ResourceScanAll, Action: rbac.ActionUpdate},
|
||||
{Resource: rbac.ResourceScanAll, Action: rbac.ActionDelete},
|
||||
{Resource: rbac.ResourceScanAll, Action: rbac.ActionList},
|
||||
|
||||
{Resource: rbac.ResourceSystemVolumes, Action: rbac.ActionRead},
|
||||
}
|
||||
)
|
39
src/common/rbac/system/rbac_user.go
Normal file
39
src/common/rbac/system/rbac_user.go
Normal file
@ -0,0 +1,39 @@
|
||||
// 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 system
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
)
|
||||
|
||||
type rbacUser struct {
|
||||
username string
|
||||
policies []*types.Policy
|
||||
}
|
||||
|
||||
// GetUserName returns username of the visitor
|
||||
func (sru *rbacUser) GetUserName() string {
|
||||
return sru.username
|
||||
}
|
||||
|
||||
// GetPolicies returns policies of the visitor
|
||||
func (sru *rbacUser) GetPolicies() []*types.Policy {
|
||||
return sru.policies
|
||||
}
|
||||
|
||||
// GetRoles returns roles of the visitor
|
||||
func (sru *rbacUser) GetRoles() []types.RBACRole {
|
||||
return nil
|
||||
}
|
@ -16,6 +16,9 @@ package robot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"github.com/goharbor/harbor/src/controller/robot"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
@ -31,7 +34,7 @@ type SecurityContext struct {
|
||||
robot *model.Robot
|
||||
isSystemLevel bool
|
||||
ctl project.Controller
|
||||
policy []*types.Policy
|
||||
policies []*types.Policy
|
||||
evaluator evaluator.Evaluator
|
||||
once sync.Once
|
||||
}
|
||||
@ -39,9 +42,10 @@ type SecurityContext struct {
|
||||
// NewSecurityContext ...
|
||||
func NewSecurityContext(robot *model.Robot, isSystemLevel bool, policy []*types.Policy) *SecurityContext {
|
||||
return &SecurityContext{
|
||||
ctl: project.Ctl,
|
||||
robot: robot,
|
||||
policy: policy,
|
||||
ctl: project.Ctl,
|
||||
robot: robot,
|
||||
policies: policy,
|
||||
isSystemLevel: isSystemLevel,
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,9 +82,25 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
||||
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||
s.once.Do(func() {
|
||||
if s.isSystemLevel {
|
||||
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy))
|
||||
var proPolicies []*types.Policy
|
||||
var sysPolicies []*types.Policy
|
||||
var evaluators evaluator.Evaluators
|
||||
for _, p := range s.policies {
|
||||
if strings.HasPrefix(p.Resource.String(), robot.SCOPESYSTEM) {
|
||||
sysPolicies = append(sysPolicies, p)
|
||||
} else if strings.HasPrefix(p.Resource.String(), robot.SCOPEPROJECT) {
|
||||
proPolicies = append(proPolicies, p)
|
||||
}
|
||||
}
|
||||
if len(sysPolicies) != 0 {
|
||||
evaluators = evaluators.Add(system.NewEvaluator(s.GetUsername(), sysPolicies))
|
||||
} else if len(proPolicies) != 0 {
|
||||
evaluators = evaluators.Add(rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), proPolicies)))
|
||||
}
|
||||
s.evaluator = evaluators
|
||||
|
||||
} else {
|
||||
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy, filterRobotPolicies))
|
||||
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policies, filterRobotPolicies))
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -13,6 +13,8 @@ const (
|
||||
|
||||
// SCOPESYSTEM ...
|
||||
SCOPESYSTEM = "/system"
|
||||
// SCOPEPROJECT ...
|
||||
SCOPEPROJECT = "/project"
|
||||
// SCOPEALLPROJECT ...
|
||||
SCOPEALLPROJECT = "/project/*"
|
||||
|
||||
|
@ -17,6 +17,7 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@ -75,7 +76,8 @@ func (l *LabelAPI) requireAccess(label *models.Label, action rbac.Action, subres
|
||||
|
||||
switch label.Scope {
|
||||
case common.LabelScopeGlobal:
|
||||
hasPermission = l.SecurityCtx.IsSysAdmin()
|
||||
resource := system.NewNamespace().Resource(rbac.ResourceLabel)
|
||||
hasPermission = l.SecurityCtx.Can(l.Context(), action, resource)
|
||||
case common.LabelScopeProject:
|
||||
if len(subresources) == 0 {
|
||||
subresources = append(subresources, rbac.ResourceLabel)
|
||||
|
@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
@ -90,7 +91,8 @@ func (w *NotificationJobAPI) List() {
|
||||
}
|
||||
|
||||
func (w *NotificationJobAPI) validateRBAC(action rbac.Action, projectID int64) bool {
|
||||
if w.SecurityCtx.IsSysAdmin() {
|
||||
resource := system.NewNamespace().Resource(rbac.ResourceNotificationPolicy)
|
||||
if w.SecurityCtx.Can(w.Context(), action, resource) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -16,11 +16,11 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/quota"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/models"
|
||||
"github.com/goharbor/harbor/src/pkg/quota/types"
|
||||
)
|
||||
|
||||
@ -32,7 +32,7 @@ type QuotaUpdateRequest struct {
|
||||
// QuotaAPI handles request to /api/quotas/
|
||||
type QuotaAPI struct {
|
||||
BaseController
|
||||
quota *models.Quota
|
||||
id int64
|
||||
}
|
||||
|
||||
// Prepare validates the URL and the user
|
||||
@ -44,11 +44,6 @@ func (qa *QuotaAPI) Prepare() {
|
||||
return
|
||||
}
|
||||
|
||||
if !qa.SecurityCtx.IsSysAdmin() {
|
||||
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
|
||||
if len(qa.GetStringFromPath(":id")) != 0 {
|
||||
id, err := qa.GetInt64FromPath(":id")
|
||||
if err != nil || id <= 0 {
|
||||
@ -61,25 +56,32 @@ func (qa *QuotaAPI) Prepare() {
|
||||
qa.SendBadRequestError(errors.New(text))
|
||||
return
|
||||
}
|
||||
|
||||
quota, err := quota.Ctl.Get(qa.Ctx.Request.Context(), id)
|
||||
if err != nil {
|
||||
qa.SendError(err)
|
||||
return
|
||||
}
|
||||
|
||||
qa.quota = quota
|
||||
qa.id = id
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns quota by id
|
||||
func (qa *QuotaAPI) Get() {
|
||||
qa.Data["json"] = qa.quota
|
||||
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionRead, rbac.ResourceQuota) {
|
||||
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
quota, err := quota.Ctl.Get(qa.Ctx.Request.Context(), qa.id)
|
||||
if err != nil {
|
||||
qa.SendError(err)
|
||||
return
|
||||
}
|
||||
qa.Data["json"] = quota
|
||||
qa.ServeJSON()
|
||||
}
|
||||
|
||||
// Put update the quota
|
||||
func (qa *QuotaAPI) Put() {
|
||||
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionUpdate, rbac.ResourceQuota) {
|
||||
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
|
||||
var req *QuotaUpdateRequest
|
||||
if err := qa.DecodeJSONReq(&req); err != nil {
|
||||
qa.SendBadRequestError(err)
|
||||
@ -87,14 +89,19 @@ func (qa *QuotaAPI) Put() {
|
||||
}
|
||||
|
||||
ctx := qa.Ctx.Request.Context()
|
||||
if err := quota.Validate(ctx, qa.quota.Reference, req.Hard); err != nil {
|
||||
q, err := quota.Ctl.Get(ctx, qa.id)
|
||||
if err != nil {
|
||||
qa.SendError(err)
|
||||
return
|
||||
}
|
||||
if err := quota.Validate(ctx, q.Reference, req.Hard); err != nil {
|
||||
qa.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
qa.quota.SetHard(req.Hard)
|
||||
q.SetHard(req.Hard)
|
||||
|
||||
if err := quota.Ctl.Update(ctx, qa.quota); err != nil {
|
||||
if err := quota.Ctl.Update(ctx, q); err != nil {
|
||||
qa.SendInternalServerError(fmt.Errorf("failed to update hard limits of the quota, error: %v", err))
|
||||
return
|
||||
}
|
||||
@ -102,6 +109,10 @@ func (qa *QuotaAPI) Put() {
|
||||
|
||||
// List returns quotas by query
|
||||
func (qa *QuotaAPI) List() {
|
||||
if !qa.SecurityCtx.Can(orm.Context(), rbac.ActionList, rbac.ResourceQuota) {
|
||||
qa.SendForbiddenError(errors.New(qa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
page, size, err := qa.GetPaginationParams()
|
||||
if err != nil {
|
||||
qa.SendBadRequestError(err)
|
||||
|
@ -3,6 +3,10 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@ -23,6 +27,7 @@ type RegistryAPI struct {
|
||||
BaseController
|
||||
manager registry.Manager
|
||||
policyCtl policy.Controller
|
||||
resource types.Resource
|
||||
}
|
||||
|
||||
// Prepare validates the user
|
||||
@ -32,11 +37,7 @@ func (t *RegistryAPI) Prepare() {
|
||||
t.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
if !t.SecurityCtx.IsSysAdmin() {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
t.resource = system.NewNamespace().Resource(rbac.ResourceRegistry)
|
||||
|
||||
t.manager = replication.RegistryMgr
|
||||
t.policyCtl = replication.PolicyCtl
|
||||
@ -44,6 +45,10 @@ func (t *RegistryAPI) Prepare() {
|
||||
|
||||
// Ping checks health status of a registry
|
||||
func (t *RegistryAPI) Ping() {
|
||||
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
req := struct {
|
||||
ID *int64 `json:"id"`
|
||||
Type *string `json:"type"`
|
||||
@ -120,6 +125,10 @@ func (t *RegistryAPI) Ping() {
|
||||
|
||||
// Get gets a registry by id.
|
||||
func (t *RegistryAPI) Get() {
|
||||
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
id, err := t.GetIDFromURL()
|
||||
if err != nil {
|
||||
t.SendBadRequestError(err)
|
||||
@ -157,6 +166,10 @@ func hideAccessSecret(credential *model.Credential) {
|
||||
|
||||
// List lists all registries
|
||||
func (t *RegistryAPI) List() {
|
||||
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionList, rbac.ResourceRegistry) {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
queryStr := t.GetString("q")
|
||||
// keep backward compatibility for the "name" query
|
||||
if len(queryStr) == 0 {
|
||||
@ -189,6 +202,10 @@ func (t *RegistryAPI) List() {
|
||||
|
||||
// Post creates a registry
|
||||
func (t *RegistryAPI) Post() {
|
||||
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionCreate, t.resource) {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := &model.Registry{}
|
||||
isValid, err := t.DecodeJSONReqAndValidate(r)
|
||||
if !isValid {
|
||||
@ -243,6 +260,10 @@ func (t *RegistryAPI) getHealthStatus(r *model.Registry) string {
|
||||
|
||||
// Put updates a registry
|
||||
func (t *RegistryAPI) Put() {
|
||||
if !t.SecurityCtx.Can(t.Context(), rbac.ActionUpdate, t.resource) {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
id, err := t.GetIDFromURL()
|
||||
if err != nil {
|
||||
t.SendBadRequestError(err)
|
||||
@ -323,6 +344,10 @@ func (t *RegistryAPI) Put() {
|
||||
|
||||
// Delete deletes a registry
|
||||
func (t *RegistryAPI) Delete() {
|
||||
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionDelete, t.resource) {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
id, err := t.GetIDFromURL()
|
||||
if err != nil {
|
||||
t.SendBadRequestError(err)
|
||||
@ -397,6 +422,10 @@ func (t *RegistryAPI) Delete() {
|
||||
|
||||
// GetInfo returns the base info and capability declarations of the registry
|
||||
func (t *RegistryAPI) GetInfo() {
|
||||
if !t.SecurityCtx.Can(orm.Context(), rbac.ActionRead, t.resource) {
|
||||
t.SendForbiddenError(errors.New(t.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
id, err := t.GetInt64FromPath(":id")
|
||||
// "0" is used for the ID of the local Harbor registry
|
||||
if err != nil || id < 0 {
|
||||
|
@ -16,6 +16,9 @@ package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/replication/adapter"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
)
|
||||
@ -23,23 +26,25 @@ import (
|
||||
// ReplicationAdapterAPI handles the replication adapter requests
|
||||
type ReplicationAdapterAPI struct {
|
||||
BaseController
|
||||
resource types.Resource
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (r *ReplicationAdapterAPI) Prepare() {
|
||||
r.BaseController.Prepare()
|
||||
if !r.SecurityCtx.IsSysAdmin() {
|
||||
if !r.SecurityCtx.IsAuthenticated() {
|
||||
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
if !r.SecurityCtx.IsAuthenticated() {
|
||||
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
r.resource = system.NewNamespace().Resource(rbac.ResourceReplicationAdapter)
|
||||
}
|
||||
|
||||
// List the replication adapters
|
||||
func (r *ReplicationAdapterAPI) List() {
|
||||
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
types := []model.RegistryType{}
|
||||
types = append(types, adapter.ListRegisteredAdapterTypes()...)
|
||||
r.WriteJSONData(types)
|
||||
@ -47,5 +52,9 @@ func (r *ReplicationAdapterAPI) List() {
|
||||
|
||||
// ListAdapterInfos the replication adapter infos
|
||||
func (r *ReplicationAdapterAPI) ListAdapterInfos() {
|
||||
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r.WriteJSONData(adapter.ListAdapterInfos())
|
||||
}
|
||||
|
@ -17,9 +17,12 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@ -36,23 +39,25 @@ import (
|
||||
// ReplicationPolicyAPI handles the replication policy requests
|
||||
type ReplicationPolicyAPI struct {
|
||||
BaseController
|
||||
resource types.Resource
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (r *ReplicationPolicyAPI) Prepare() {
|
||||
r.BaseController.Prepare()
|
||||
if !r.SecurityCtx.IsSysAdmin() {
|
||||
if !r.SecurityCtx.IsAuthenticated() {
|
||||
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
if !r.SecurityCtx.IsAuthenticated() {
|
||||
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
r.resource = system.NewNamespace().Resource(rbac.ResourceReplicationPolicy)
|
||||
}
|
||||
|
||||
// List the replication policies
|
||||
func (r *ReplicationPolicyAPI) List() {
|
||||
if !r.SecurityCtx.Can(r.Context(), rbac.ActionList, r.resource) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
page, size, err := r.GetPaginationParams()
|
||||
if err != nil {
|
||||
r.SendInternalServerError(err)
|
||||
@ -82,6 +87,10 @@ func (r *ReplicationPolicyAPI) List() {
|
||||
|
||||
// Create the replication policy
|
||||
func (r *ReplicationPolicyAPI) Create() {
|
||||
if !r.SecurityCtx.Can(r.Context(), rbac.ActionCreate, r.resource) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
policy := &model.Policy{}
|
||||
isValid, err := r.DecodeJSONReqAndValidate(policy)
|
||||
if !isValid {
|
||||
@ -141,6 +150,10 @@ func (r *ReplicationPolicyAPI) validateRegistry(policy *model.Policy) bool {
|
||||
|
||||
// Get the specified replication policy
|
||||
func (r *ReplicationPolicyAPI) Get() {
|
||||
if !r.SecurityCtx.Can(r.Context(), rbac.ActionRead, r.resource) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
id, err := r.GetInt64FromPath(":id")
|
||||
if id <= 0 || err != nil {
|
||||
r.SendBadRequestError(errors.New("invalid policy ID"))
|
||||
@ -166,6 +179,10 @@ func (r *ReplicationPolicyAPI) Get() {
|
||||
|
||||
// Update the replication policy
|
||||
func (r *ReplicationPolicyAPI) Update() {
|
||||
if !r.SecurityCtx.Can(r.Context(), rbac.ActionUpdate, r.resource) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
id, err := r.GetInt64FromPath(":id")
|
||||
if id <= 0 || err != nil {
|
||||
r.SendBadRequestError(errors.New("invalid policy ID"))
|
||||
@ -207,6 +224,10 @@ func (r *ReplicationPolicyAPI) Update() {
|
||||
|
||||
// Delete the replication policy
|
||||
func (r *ReplicationPolicyAPI) Delete() {
|
||||
if !r.SecurityCtx.Can(r.Context(), rbac.ActionDelete, r.resource) {
|
||||
r.SendForbiddenError(errors.New(r.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
id, err := r.GetInt64FromPath(":id")
|
||||
if id <= 0 || err != nil {
|
||||
r.SendBadRequestError(errors.New("invalid policy ID"))
|
||||
|
@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/task"
|
||||
"net/http"
|
||||
@ -435,7 +436,8 @@ func (r *RetentionAPI) requireAccess(p *policy.Metadata, action rbac.Action, sub
|
||||
}
|
||||
hasPermission, _ = r.HasProjectPermission(p.Scope.Reference, action, subresources...)
|
||||
default:
|
||||
hasPermission = r.SecurityCtx.IsSysAdmin()
|
||||
resource := system.NewNamespace().Resource(rbac.ResourceTagRetention)
|
||||
hasPermission = r.SecurityCtx.Can(r.Context(), action, resource)
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
|
@ -16,6 +16,9 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"net/http"
|
||||
|
||||
s "github.com/goharbor/harbor/src/controller/scanner"
|
||||
@ -31,6 +34,8 @@ type ScannerAPI struct {
|
||||
|
||||
// Controller for the plug scanners
|
||||
c s.Controller
|
||||
|
||||
resource types.Resource
|
||||
}
|
||||
|
||||
// Prepare sth. for the subsequent actions
|
||||
@ -44,10 +49,7 @@ func (sa *ScannerAPI) Prepare() {
|
||||
return
|
||||
}
|
||||
|
||||
if !sa.SecurityCtx.IsSysAdmin() {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
sa.resource = system.NewNamespace().Resource(rbac.ResourceScanner)
|
||||
|
||||
// Use the default controller
|
||||
sa.c = s.DefaultController
|
||||
@ -55,6 +57,10 @@ func (sa *ScannerAPI) Prepare() {
|
||||
|
||||
// Get the specified scanner
|
||||
func (sa *ScannerAPI) Get() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
if r := sa.get(); r != nil {
|
||||
// Response to the client
|
||||
sa.Data["json"] = r
|
||||
@ -64,6 +70,10 @@ func (sa *ScannerAPI) Get() {
|
||||
|
||||
// Metadata returns the metadata of the given scanner.
|
||||
func (sa *ScannerAPI) Metadata() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
uuid := sa.GetStringFromPath(":uuid")
|
||||
|
||||
meta, err := sa.c.GetMetadata(uuid)
|
||||
@ -79,6 +89,10 @@ func (sa *ScannerAPI) Metadata() {
|
||||
|
||||
// List all the scanners
|
||||
func (sa *ScannerAPI) List() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionList, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
p, pz, err := sa.GetPaginationParams()
|
||||
if err != nil {
|
||||
sa.SendBadRequestError(errors.Wrap(err, "scanner API: list all"))
|
||||
@ -117,6 +131,10 @@ func (sa *ScannerAPI) List() {
|
||||
|
||||
// Create a new scanner
|
||||
func (sa *ScannerAPI) Create() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionCreate, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := &scanner.Registration{}
|
||||
|
||||
if err := sa.DecodeJSONReq(r); err != nil {
|
||||
@ -158,6 +176,10 @@ func (sa *ScannerAPI) Create() {
|
||||
|
||||
// Update a scanner
|
||||
func (sa *ScannerAPI) Update() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionUpdate, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := sa.get()
|
||||
if r == nil {
|
||||
// meet error
|
||||
@ -213,6 +235,10 @@ func (sa *ScannerAPI) Update() {
|
||||
|
||||
// Delete the scanner
|
||||
func (sa *ScannerAPI) Delete() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionDelete, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := sa.get()
|
||||
if r == nil {
|
||||
// meet error
|
||||
@ -237,6 +263,10 @@ func (sa *ScannerAPI) Delete() {
|
||||
|
||||
// SetAsDefault sets the given registration as default one
|
||||
func (sa *ScannerAPI) SetAsDefault() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionCreate, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
uid := sa.GetStringFromPath(":uuid")
|
||||
|
||||
m := make(map[string]interface{})
|
||||
@ -261,6 +291,10 @@ func (sa *ScannerAPI) SetAsDefault() {
|
||||
|
||||
// Ping the registration.
|
||||
func (sa *ScannerAPI) Ping() {
|
||||
if !sa.SecurityCtx.Can(sa.Context(), rbac.ActionRead, sa.resource) {
|
||||
sa.SendForbiddenError(errors.New(sa.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
r := &scanner.Registration{}
|
||||
|
||||
if err := sa.DecodeJSONReq(r); err != nil {
|
||||
|
@ -15,8 +15,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
@ -39,9 +41,9 @@ type UserAPI struct {
|
||||
currentUserID int
|
||||
userID int
|
||||
SelfRegistration bool
|
||||
IsAdmin bool
|
||||
AuthMode string
|
||||
secretKey string
|
||||
resource types.Resource
|
||||
}
|
||||
|
||||
type passwordReq struct {
|
||||
@ -137,12 +139,12 @@ func (ua *UserAPI) Prepare() {
|
||||
}
|
||||
}
|
||||
|
||||
ua.IsAdmin = ua.SecurityCtx.IsSysAdmin()
|
||||
ua.resource = system.NewNamespace().Resource(rbac.ResourceUser)
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (ua *UserAPI) Get() {
|
||||
if ua.userID == ua.currentUserID || ua.IsAdmin {
|
||||
if ua.userID == ua.currentUserID || ua.SecurityCtx.Can(ua.Context(), rbac.ActionRead, ua.resource) {
|
||||
userQuery := models.User{UserID: ua.userID}
|
||||
u, err := dao.GetUser(userQuery)
|
||||
if err != nil {
|
||||
@ -178,7 +180,7 @@ func (ua *UserAPI) Get() {
|
||||
|
||||
// List ...
|
||||
func (ua *UserAPI) List() {
|
||||
if !ua.IsAdmin {
|
||||
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionList, ua.resource) {
|
||||
log.Errorf("Current user, id: %d does not have admin role, can not list users", ua.currentUserID)
|
||||
ua.SendForbiddenError(errors.New("user does not have admin role"))
|
||||
return
|
||||
@ -262,7 +264,7 @@ func (ua *UserAPI) Search() {
|
||||
|
||||
// Put ...
|
||||
func (ua *UserAPI) Put() {
|
||||
if !ua.modifiable() {
|
||||
if !ua.modifiable(ua.Context()) {
|
||||
ua.SendForbiddenError(fmt.Errorf("User with ID %d cannot be modified", ua.userID))
|
||||
return
|
||||
}
|
||||
@ -318,13 +320,13 @@ func (ua *UserAPI) Post() {
|
||||
return
|
||||
}
|
||||
|
||||
if !(ua.SelfRegistration || ua.IsAdmin) {
|
||||
if !(ua.SelfRegistration || ua.SecurityCtx.Can(ua.Context(), rbac.ActionCreate, ua.resource)) {
|
||||
log.Warning("Registration can only be used by admin role user when self-registration is off.")
|
||||
ua.SendForbiddenError(errors.New(""))
|
||||
return
|
||||
}
|
||||
|
||||
if !ua.IsAdmin && !lib.GetCarrySession(ua.Ctx.Request.Context()) {
|
||||
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionCreate, ua.resource) && !lib.GetCarrySession(ua.Ctx.Request.Context()) {
|
||||
ua.SendForbiddenError(errors.New("self-registration cannot be triggered via API"))
|
||||
return
|
||||
}
|
||||
@ -341,7 +343,7 @@ func (ua *UserAPI) Post() {
|
||||
return
|
||||
}
|
||||
|
||||
if !ua.IsAdmin && user.SysAdminFlag {
|
||||
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionCreate, ua.resource) && user.SysAdminFlag {
|
||||
msg := "Non-admin cannot create an admin user."
|
||||
log.Errorf(msg)
|
||||
ua.SendForbiddenError(errors.New(msg))
|
||||
@ -383,7 +385,7 @@ func (ua *UserAPI) Post() {
|
||||
|
||||
// Delete ...
|
||||
func (ua *UserAPI) Delete() {
|
||||
if !ua.IsAdmin || ua.AuthMode != common.DBAuth || ua.userID == 1 || ua.currentUserID == ua.userID {
|
||||
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionDelete, ua.resource) || ua.AuthMode != common.DBAuth || ua.userID == 1 || ua.currentUserID == ua.userID {
|
||||
ua.SendForbiddenError(fmt.Errorf("User with ID: %d cannot be removed, auth mode: %s, current user ID: %d", ua.userID, ua.AuthMode, ua.currentUserID))
|
||||
return
|
||||
}
|
||||
@ -399,7 +401,7 @@ func (ua *UserAPI) Delete() {
|
||||
|
||||
// ChangePassword handles PUT to /api/users/{}/password
|
||||
func (ua *UserAPI) ChangePassword() {
|
||||
if !ua.modifiable() {
|
||||
if !ua.modifiable(ua.Context()) {
|
||||
ua.SendForbiddenError(fmt.Errorf("User with ID: %d is not modifiable", ua.userID))
|
||||
return
|
||||
}
|
||||
@ -456,7 +458,7 @@ func (ua *UserAPI) ChangePassword() {
|
||||
|
||||
// ToggleUserAdminRole handles PUT api/users/{}/sysadmin
|
||||
func (ua *UserAPI) ToggleUserAdminRole() {
|
||||
if !ua.IsAdmin {
|
||||
if !ua.SecurityCtx.Can(ua.Context(), rbac.ActionUpdate, ua.resource) {
|
||||
log.Warningf("current user, id: %d does not have admin role, can not update other user's role", ua.currentUserID)
|
||||
ua.RenderError(http.StatusForbidden, "User does not have admin role")
|
||||
return
|
||||
@ -527,7 +529,7 @@ func (ua *UserAPI) SetCLISecret() {
|
||||
ua.SendPreconditionFailedError(errors.New("the auth mode has to be oidc auth"))
|
||||
return
|
||||
}
|
||||
if ua.userID != ua.currentUserID && !ua.IsAdmin {
|
||||
if ua.userID != ua.currentUserID && !ua.SecurityCtx.Can(ua.Context(), rbac.ActionUpdate, ua.resource) {
|
||||
ua.SendForbiddenError(errors.New(""))
|
||||
return
|
||||
}
|
||||
@ -584,10 +586,10 @@ func (ua *UserAPI) getOIDCUserInfo() (*models.OIDCUser, error) {
|
||||
}
|
||||
|
||||
// modifiable returns whether the modify is allowed based on current auth mode and context
|
||||
func (ua *UserAPI) modifiable() bool {
|
||||
func (ua *UserAPI) modifiable(ctx context.Context) bool {
|
||||
if ua.AuthMode == common.DBAuth {
|
||||
// When the auth mode is local DB, admin can modify anyone, non-admin can modify himself.
|
||||
return ua.IsAdmin || ua.userID == ua.currentUserID
|
||||
return ua.SecurityCtx.Can(ctx, rbac.ActionUpdate, ua.resource) || ua.userID == ua.currentUserID
|
||||
}
|
||||
// When the auth mode is external IDM backend, only the super user can modify himself,
|
||||
// because he's the only one whose information is stored in local DB.
|
||||
|
@ -14,7 +14,10 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
securitytesting "github.com/goharbor/harbor/src/testing/common/security"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
@ -617,45 +620,50 @@ func TestModifiable(t *testing.T) {
|
||||
BaseAPI: api.BaseAPI{
|
||||
Controller: beego.Controller{},
|
||||
},
|
||||
SecurityCtx: nil,
|
||||
}
|
||||
|
||||
security := &securitytesting.Context{}
|
||||
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
|
||||
base.SecurityCtx = security
|
||||
|
||||
ua1 := &UserAPI{
|
||||
BaseController: base,
|
||||
currentUserID: 3,
|
||||
userID: 4,
|
||||
SelfRegistration: false,
|
||||
IsAdmin: false,
|
||||
AuthMode: "db_auth",
|
||||
}
|
||||
assert.False(ua1.modifiable())
|
||||
assert.False(ua1.modifiable(context.TODO()))
|
||||
|
||||
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
|
||||
ua2 := &UserAPI{
|
||||
BaseController: base,
|
||||
currentUserID: 3,
|
||||
userID: 4,
|
||||
SelfRegistration: false,
|
||||
IsAdmin: true,
|
||||
AuthMode: "db_auth",
|
||||
}
|
||||
assert.True(ua2.modifiable())
|
||||
assert.True(ua2.modifiable(context.TODO()))
|
||||
|
||||
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
|
||||
ua3 := &UserAPI{
|
||||
BaseController: base,
|
||||
currentUserID: 3,
|
||||
userID: 4,
|
||||
SelfRegistration: false,
|
||||
IsAdmin: true,
|
||||
AuthMode: "ldap_auth",
|
||||
}
|
||||
assert.False(ua3.modifiable())
|
||||
assert.False(ua3.modifiable(context.TODO()))
|
||||
|
||||
security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
|
||||
ua4 := &UserAPI{
|
||||
BaseController: base,
|
||||
currentUserID: 1,
|
||||
userID: 1,
|
||||
SelfRegistration: false,
|
||||
IsAdmin: true,
|
||||
AuthMode: "ldap_auth",
|
||||
}
|
||||
assert.True(ua4.modifiable())
|
||||
assert.True(ua4.modifiable(context.TODO()))
|
||||
}
|
||||
|
||||
func TestUsersCurrentPermissions(t *testing.T) {
|
||||
|
@ -17,6 +17,8 @@ package api
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -58,11 +60,6 @@ func (uga *UserGroupAPI) Prepare() {
|
||||
return
|
||||
}
|
||||
uga.id = int(ugid)
|
||||
// Common user can create/update, only harbor admin can delete user group.
|
||||
if uga.Ctx.Input.IsDelete() && !uga.SecurityCtx.IsSysAdmin() {
|
||||
uga.SendForbiddenError(errors.New(uga.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
authMode, err := config.AuthMode()
|
||||
if err != nil {
|
||||
uga.SendInternalServerError(errors.New("failed to get authentication mode"))
|
||||
@ -192,6 +189,11 @@ func (uga *UserGroupAPI) Put() {
|
||||
|
||||
// Delete ...
|
||||
func (uga *UserGroupAPI) Delete() {
|
||||
resource := system.NewNamespace().Resource(rbac.ResourceUserGroup)
|
||||
if !uga.SecurityCtx.Can(uga.Context(), rbac.ActionDelete, resource) {
|
||||
uga.SendForbiddenError(errors.New(uga.SecurityCtx.GetUsername()))
|
||||
return
|
||||
}
|
||||
err := group.DeleteUserGroup(uga.id)
|
||||
if err != nil {
|
||||
uga.SendInternalServerError(fmt.Errorf("Error occurred in update user group, error: %v", err))
|
||||
|
@ -17,6 +17,7 @@ package v2auth
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -52,8 +53,11 @@ func (rc *reqChecker) check(req *http.Request) (string, error) {
|
||||
if a.target == login && !securityCtx.IsAuthenticated() {
|
||||
return getChallenge(req, al), errors.New("unauthorized")
|
||||
}
|
||||
if a.target == catalog && !securityCtx.IsSysAdmin() {
|
||||
return getChallenge(req, al), fmt.Errorf("unauthorized to list catalog")
|
||||
if a.target == catalog {
|
||||
resource := system.NewNamespace().Resource(rbac.ResourceCatalog)
|
||||
if !securityCtx.Can(req.Context(), rbac.ActionRead, resource) {
|
||||
return getChallenge(req, al), fmt.Errorf("unauthorized to list catalog")
|
||||
}
|
||||
}
|
||||
if a.target == repository && req.Header.Get(authHeader) == "" && req.Method == http.MethodHead { // make sure 401 is returned for CLI HEAD, see #11271
|
||||
return getChallenge(req, al), fmt.Errorf("authorize header needed to send HEAD to repository")
|
||||
|
@ -3,7 +3,6 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
@ -44,7 +43,7 @@ func (a *auditlogAPI) ListAuditLogs(ctx context.Context, params auditlog.ListAud
|
||||
return a.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if !secCtx.IsSysAdmin() {
|
||||
if err := a.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceAuditLog); err != nil {
|
||||
ol := &q.OrList{}
|
||||
if sc, ok := secCtx.(*local.SecurityContext); ok && sc.IsAuthenticated() {
|
||||
user := sc.User()
|
||||
|
@ -18,6 +18,7 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/rbac/system"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
@ -105,8 +106,8 @@ func (b *BaseAPI) RequireProjectAccess(ctx context.Context, projectIDOrName inte
|
||||
return errors.ForbiddenError(nil)
|
||||
}
|
||||
|
||||
// RequireSysAdmin checks the system admin permission according to the security context
|
||||
func (b *BaseAPI) RequireSysAdmin(ctx context.Context) error {
|
||||
// RequireSystemAccess checks the system admin permission according to the security context
|
||||
func (b *BaseAPI) RequireSystemAccess(ctx context.Context, action rbac.Action, subresource ...rbac.Resource) error {
|
||||
secCtx, ok := security.FromContext(ctx)
|
||||
if !ok {
|
||||
return errors.UnauthorizedError(errors.New("security context not found"))
|
||||
@ -114,7 +115,8 @@ func (b *BaseAPI) RequireSysAdmin(ctx context.Context) error {
|
||||
if !secCtx.IsAuthenticated() {
|
||||
return errors.UnauthorizedError(nil)
|
||||
}
|
||||
if !secCtx.IsSysAdmin() {
|
||||
resource := system.NewNamespace().Resource(subresource...)
|
||||
if !secCtx.Can(ctx, action, resource) {
|
||||
return errors.ForbiddenError(nil).WithMessage(secCtx.GetUsername())
|
||||
}
|
||||
return nil
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/gc"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
@ -30,13 +31,13 @@ func newGCAPI() *gcAPI {
|
||||
}
|
||||
|
||||
func (g *gcAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
|
||||
if err := g.RequireSysAdmin(ctx); err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *gcAPI) CreateGCSchedule(ctx context.Context, params operation.CreateGCScheduleParams) middleware.Responder {
|
||||
if err := g.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceGarbageCollection); err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
}
|
||||
id, err := g.kick(ctx, params.Schedule.Schedule.Type, params.Schedule.Schedule.Cron, params.Schedule.Parameters)
|
||||
if err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
@ -51,6 +52,9 @@ func (g *gcAPI) CreateGCSchedule(ctx context.Context, params operation.CreateGCS
|
||||
}
|
||||
|
||||
func (g *gcAPI) UpdateGCSchedule(ctx context.Context, params operation.UpdateGCScheduleParams) middleware.Responder {
|
||||
if err := g.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourceGarbageCollection); err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
}
|
||||
_, err := g.kick(ctx, params.Schedule.Schedule.Type, params.Schedule.Schedule.Cron, params.Schedule.Parameters)
|
||||
if err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
@ -114,6 +118,9 @@ func (g *gcAPI) updateSchedule(ctx context.Context, cronType, cron string, polic
|
||||
}
|
||||
|
||||
func (g *gcAPI) GetGCSchedule(ctx context.Context, params operation.GetGCScheduleParams) middleware.Responder {
|
||||
if err := g.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceGarbageCollection); err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
}
|
||||
schedule, err := g.gcCtr.GetSchedule(ctx)
|
||||
if errors.IsNotFoundErr(err) {
|
||||
return operation.NewGetGCScheduleOK()
|
||||
@ -126,6 +133,9 @@ func (g *gcAPI) GetGCSchedule(ctx context.Context, params operation.GetGCSchedul
|
||||
}
|
||||
|
||||
func (g *gcAPI) GetGCHistory(ctx context.Context, params operation.GetGCHistoryParams) middleware.Responder {
|
||||
if err := g.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceGarbageCollection); err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
}
|
||||
query, err := g.BuildQuery(ctx, params.Q, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
@ -171,6 +181,9 @@ func (g *gcAPI) GetGCHistory(ctx context.Context, params operation.GetGCHistoryP
|
||||
}
|
||||
|
||||
func (g *gcAPI) GetGC(ctx context.Context, params operation.GetGCParams) middleware.Responder {
|
||||
if err := g.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceGarbageCollection); err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
}
|
||||
exec, err := g.gcCtr.GetExecution(ctx, params.GCID)
|
||||
if err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
@ -198,6 +211,9 @@ func (g *gcAPI) GetGC(ctx context.Context, params operation.GetGCParams) middlew
|
||||
}
|
||||
|
||||
func (g *gcAPI) GetGCLog(ctx context.Context, params operation.GetGCLogParams) middleware.Responder {
|
||||
if err := g.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceGarbageCollection); err != nil {
|
||||
return g.SendError(ctx, err)
|
||||
}
|
||||
tasks, err := g.gcCtr.ListTasks(ctx, q.New(q.KeyWords{
|
||||
"ExecutionID": params.GCID,
|
||||
}))
|
||||
|
@ -3,7 +3,6 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -16,6 +15,7 @@ import (
|
||||
projectCtl "github.com/goharbor/harbor/src/controller/project"
|
||||
taskCtl "github.com/goharbor/harbor/src/controller/task"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
liberrors "github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/p2p/preheat/models/policy"
|
||||
@ -56,7 +56,7 @@ func (api *preheatAPI) Prepare(ctx context.Context, operation string, params int
|
||||
}
|
||||
|
||||
func (api *preheatAPI) CreateInstance(ctx context.Context, params operation.CreateInstanceParams) middleware.Responder {
|
||||
if err := api.RequireSysAdmin(ctx); err != nil {
|
||||
if err := api.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourcePreatInstance); err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ func (api *preheatAPI) CreateInstance(ctx context.Context, params operation.Crea
|
||||
}
|
||||
|
||||
func (api *preheatAPI) DeleteInstance(ctx context.Context, params operation.DeleteInstanceParams) middleware.Responder {
|
||||
if err := api.RequireSysAdmin(ctx); err != nil {
|
||||
if err := api.RequireSystemAccess(ctx, rbac.ActionDelete, rbac.ResourcePreatInstance); err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -93,7 +93,7 @@ func (api *preheatAPI) DeleteInstance(ctx context.Context, params operation.Dele
|
||||
}
|
||||
|
||||
func (api *preheatAPI) GetInstance(ctx context.Context, params operation.GetInstanceParams) middleware.Responder {
|
||||
if err := api.RequireSysAdmin(ctx); err != nil {
|
||||
if err := api.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourcePreatInstance); err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ func (api *preheatAPI) GetInstance(ctx context.Context, params operation.GetInst
|
||||
|
||||
// ListInstances is List p2p instances
|
||||
func (api *preheatAPI) ListInstances(ctx context.Context, params operation.ListInstancesParams) middleware.Responder {
|
||||
if err := api.RequireSysAdmin(ctx); err != nil {
|
||||
if err := api.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourcePreatInstance); err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -147,7 +147,7 @@ func (api *preheatAPI) ListInstances(ctx context.Context, params operation.ListI
|
||||
}
|
||||
|
||||
func (api *preheatAPI) ListProviders(ctx context.Context, params operation.ListProvidersParams) middleware.Responder {
|
||||
if err := api.RequireSysAdmin(ctx); err != nil {
|
||||
if err := api.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourcePreatInstance); err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ func (api *preheatAPI) ListProviders(ctx context.Context, params operation.ListP
|
||||
|
||||
// UpdateInstance is Update instance
|
||||
func (api *preheatAPI) UpdateInstance(ctx context.Context, params operation.UpdateInstanceParams) middleware.Responder {
|
||||
if err := api.RequireSysAdmin(ctx); err != nil {
|
||||
if err := api.RequireSystemAccess(ctx, rbac.ActionUpdate, rbac.ResourcePreatInstance); err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
@ -387,7 +387,7 @@ func (api *preheatAPI) ManualPreheat(ctx context.Context, params operation.Manua
|
||||
}
|
||||
|
||||
func (api *preheatAPI) PingInstances(ctx context.Context, params operation.PingInstancesParams) middleware.Responder {
|
||||
if err := api.RequireSysAdmin(ctx); err != nil {
|
||||
if err := api.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourcePreatInstance); err != nil {
|
||||
return api.SendError(ctx, err)
|
||||
}
|
||||
|
||||
|
@ -76,22 +76,21 @@ func (a *projectAPI) CreateProject(ctx context.Context, params operation.CreateP
|
||||
}
|
||||
|
||||
secCtx, _ := security.FromContext(ctx)
|
||||
if onlyAdmin && !(secCtx.IsSysAdmin() || secCtx.IsSolutionUser()) {
|
||||
if onlyAdmin && !(a.isSysAdmin(ctx, rbac.ActionCreate) || secCtx.IsSolutionUser()) {
|
||||
log.Errorf("Only sys admin can create project")
|
||||
return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create project"))
|
||||
}
|
||||
|
||||
req := params.Project
|
||||
|
||||
if req.RegistryID != nil && !secCtx.IsSysAdmin() {
|
||||
// only system admin can create the proxy cache project
|
||||
if req.RegistryID != nil && !a.isSysAdmin(ctx, rbac.ActionCreate) {
|
||||
return a.SendError(ctx, errors.ForbiddenError(nil).WithMessage("Only system admin can create proxy cache project"))
|
||||
}
|
||||
|
||||
// populate storage limit
|
||||
if config.QuotaPerProjectEnable() {
|
||||
// the security context is not sys admin, set the StorageLimit the global StoragePerProject
|
||||
if req.StorageLimit == nil || *req.StorageLimit == 0 || !secCtx.IsSysAdmin() {
|
||||
if req.StorageLimit == nil || *req.StorageLimit == 0 || !a.isSysAdmin(ctx, rbac.ActionCreate) {
|
||||
setting, err := config.QuotaSetting()
|
||||
if err != nil {
|
||||
log.Errorf("failed to get quota setting: %v", err)
|
||||
@ -378,7 +377,7 @@ func (a *projectAPI) ListProjects(ctx context.Context, params operation.ListProj
|
||||
|
||||
secCtx, ok := security.FromContext(ctx)
|
||||
if ok && secCtx.IsAuthenticated() {
|
||||
if !secCtx.IsSysAdmin() && !secCtx.IsSolutionUser() {
|
||||
if !a.isSysAdmin(ctx, rbac.ActionList) && !secCtx.IsSolutionUser() {
|
||||
// authenticated but not system admin or solution user,
|
||||
// return public projects and projects that the user is member of
|
||||
if l, ok := secCtx.(*local.SecurityContext); ok {
|
||||
@ -585,6 +584,13 @@ func (a *projectAPI) populateProperties(ctx context.Context, p *project.Project)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *projectAPI) isSysAdmin(ctx context.Context, action rbac.Action) bool {
|
||||
if err := a.RequireSystemAccess(ctx, action, rbac.ResourceProject); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getProjectQuotaSummary(ctx context.Context, p *project.Project, summary *models.ProjectSummary) {
|
||||
if !config.QuotaPerProjectEnable() {
|
||||
log.Debug("Quota per project disabled")
|
||||
|
@ -16,6 +16,7 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@ -48,14 +49,14 @@ type replicationAPI struct {
|
||||
}
|
||||
|
||||
func (r *replicationAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
|
||||
if err := r.RequireSysAdmin(ctx); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *replicationAPI) StartReplication(ctx context.Context, params operation.StartReplicationParams) middleware.Responder {
|
||||
// TODO move the following logic to the replication controller after refactoring the policy management part with the new programming model
|
||||
if err := r.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceReplication); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
policy, err := r.policyMgr.Get(params.Execution.PolicyID)
|
||||
if err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
@ -85,6 +86,9 @@ func (r *replicationAPI) StartReplication(ctx context.Context, params operation.
|
||||
}
|
||||
|
||||
func (r *replicationAPI) StopReplication(ctx context.Context, params operation.StopReplicationParams) middleware.Responder {
|
||||
if err := r.RequireSystemAccess(ctx, rbac.ActionCreate, rbac.ResourceReplication); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
if err := r.ctl.Stop(ctx, params.ID); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
@ -92,6 +96,9 @@ func (r *replicationAPI) StopReplication(ctx context.Context, params operation.S
|
||||
}
|
||||
|
||||
func (r *replicationAPI) ListReplicationExecutions(ctx context.Context, params operation.ListReplicationExecutionsParams) middleware.Responder {
|
||||
if err := r.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceReplication); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
query, err := r.BuildQuery(ctx, nil, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
@ -151,6 +158,9 @@ func (r *replicationAPI) ListReplicationExecutions(ctx context.Context, params o
|
||||
}
|
||||
|
||||
func (r *replicationAPI) GetReplicationExecution(ctx context.Context, params operation.GetReplicationExecutionParams) middleware.Responder {
|
||||
if err := r.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceReplication); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
execution, err := r.ctl.GetExecution(ctx, params.ID)
|
||||
if err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
@ -159,6 +169,9 @@ func (r *replicationAPI) GetReplicationExecution(ctx context.Context, params ope
|
||||
}
|
||||
|
||||
func (r *replicationAPI) ListReplicationTasks(ctx context.Context, params operation.ListReplicationTasksParams) middleware.Responder {
|
||||
if err := r.RequireSystemAccess(ctx, rbac.ActionList, rbac.ResourceReplication); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
query, err := r.BuildQuery(ctx, nil, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
@ -210,6 +223,9 @@ func (r *replicationAPI) ListReplicationTasks(ctx context.Context, params operat
|
||||
}
|
||||
|
||||
func (r *replicationAPI) GetReplicationLog(ctx context.Context, params operation.GetReplicationLogParams) middleware.Responder {
|
||||
if err := r.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceReplication); err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
}
|
||||
execution, err := r.ctl.GetExecution(ctx, params.ID)
|
||||
if err != nil {
|
||||
return r.SendError(ctx, err)
|
||||
@ -229,7 +245,6 @@ func (r *replicationAPI) GetReplicationLog(ctx context.Context, params operation
|
||||
}
|
||||
return operation.NewGetReplicationLogOK().WithContentType("text/plain").WithPayload(string(log))
|
||||
}
|
||||
|
||||
func convertExecution(execution *replication.Execution) *models.ReplicationExecution {
|
||||
exec := &models.ReplicationExecution{
|
||||
ID: execution.ID,
|
||||
|
@ -244,7 +244,7 @@ func (rAPI *robotAPI) RefreshSec(ctx context.Context, params operation.RefreshSe
|
||||
|
||||
func (rAPI *robotAPI) requireAccess(ctx context.Context, level string, projectIDOrName interface{}, action rbac.Action) error {
|
||||
if level == robot.LEVELSYSTEM {
|
||||
return rAPI.RequireSysAdmin(ctx)
|
||||
return rAPI.RequireSystemAccess(ctx, action, rbac.ResourceRobot)
|
||||
} else if level == robot.LEVELPROJECT {
|
||||
return rAPI.RequireProjectAccess(ctx, projectIDOrName, action, rbac.ResourceRobot)
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/scan"
|
||||
"github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
@ -50,18 +51,14 @@ type scanAllAPI struct {
|
||||
}
|
||||
|
||||
func (s *scanAllAPI) Prepare(ctx context.Context, operation string, params interface{}) middleware.Responder {
|
||||
if err := s.RequireSysAdmin(ctx); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := s.requireScanEnabled(ctx); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scanAllAPI) CreateScanAllSchedule(ctx context.Context, params operation.CreateScanAllScheduleParams) middleware.Responder {
|
||||
if err := s.requireAccess(ctx, rbac.ActionCreate); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
|
||||
req := params.Schedule
|
||||
|
||||
if req.Schedule.Type == ScheduleNone {
|
||||
@ -102,6 +99,9 @@ func (s *scanAllAPI) CreateScanAllSchedule(ctx context.Context, params operation
|
||||
}
|
||||
|
||||
func (s *scanAllAPI) UpdateScanAllSchedule(ctx context.Context, params operation.UpdateScanAllScheduleParams) middleware.Responder {
|
||||
if err := s.requireAccess(ctx, rbac.ActionUpdate); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
req := params.Schedule
|
||||
|
||||
if req.Schedule.Type == ScheduleManual {
|
||||
@ -130,6 +130,9 @@ func (s *scanAllAPI) UpdateScanAllSchedule(ctx context.Context, params operation
|
||||
}
|
||||
|
||||
func (s *scanAllAPI) GetScanAllSchedule(ctx context.Context, params operation.GetScanAllScheduleParams) middleware.Responder {
|
||||
if err := s.requireAccess(ctx, rbac.ActionRead); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
schedule, err := s.getScanAllSchedule(ctx)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
@ -139,6 +142,9 @@ func (s *scanAllAPI) GetScanAllSchedule(ctx context.Context, params operation.Ge
|
||||
}
|
||||
|
||||
func (s *scanAllAPI) GetLatestScanAllMetrics(ctx context.Context, params operation.GetLatestScanAllMetricsParams) middleware.Responder {
|
||||
if err := s.requireAccess(ctx, rbac.ActionRead); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
stats, err := s.getMetrics(ctx)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
@ -148,6 +154,9 @@ func (s *scanAllAPI) GetLatestScanAllMetrics(ctx context.Context, params operati
|
||||
}
|
||||
|
||||
func (s *scanAllAPI) GetLatestScheduledScanAllMetrics(ctx context.Context, params operation.GetLatestScheduledScanAllMetricsParams) middleware.Responder {
|
||||
if err := s.requireAccess(ctx, rbac.ActionRead); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
stats, err := s.getMetrics(ctx, task.ExecutionTriggerSchedule)
|
||||
if err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
@ -259,3 +268,13 @@ func (s *scanAllAPI) requireScanEnabled(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *scanAllAPI) requireAccess(ctx context.Context, action rbac.Action) error {
|
||||
if err := s.RequireSystemAccess(ctx, action, rbac.ResourceScanAll); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.requireScanEnabled(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
|
||||
{
|
||||
// system admin required
|
||||
suite.Security.On("IsAuthenticated").Return(true).Once()
|
||||
suite.Security.On("IsSysAdmin").Return(false).Once()
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(false).Once()
|
||||
suite.Security.On("GetUsername").Return("username").Once()
|
||||
|
||||
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
|
||||
@ -141,8 +141,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
|
||||
{
|
||||
// default scanner required
|
||||
suite.Security.On("IsAuthenticated").Return(true).Once()
|
||||
suite.Security.On("IsSysAdmin").Return(true).Once()
|
||||
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, nil).Once()
|
||||
|
||||
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
|
||||
@ -153,8 +152,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
|
||||
{
|
||||
// default scanner required failed
|
||||
suite.Security.On("IsAuthenticated").Return(true).Once()
|
||||
suite.Security.On("IsSysAdmin").Return(true).Once()
|
||||
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Once()
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return(nil, fmt.Errorf("failed")).Once()
|
||||
|
||||
res, err := suite.DoReq(req.method, req.url, newBody(req.body))
|
||||
@ -167,7 +165,7 @@ func (suite *ScanAllTestSuite) TestAuthorization() {
|
||||
func (suite *ScanAllTestSuite) TestGetLatestScanAllMetrics() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("IsSysAdmin").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
|
||||
|
||||
{
|
||||
@ -205,7 +203,7 @@ func (suite *ScanAllTestSuite) TestGetLatestScanAllMetrics() {
|
||||
func (suite *ScanAllTestSuite) TestGetLatestScheduledScanAllMetrics() {
|
||||
times := 3
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("IsSysAdmin").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
|
||||
|
||||
{
|
||||
@ -243,7 +241,7 @@ func (suite *ScanAllTestSuite) TestGetLatestScheduledScanAllMetrics() {
|
||||
func (suite *ScanAllTestSuite) TestCreateScanAllSchedule() {
|
||||
times := 11
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("IsSysAdmin").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
|
||||
|
||||
{
|
||||
@ -356,7 +354,7 @@ func (suite *ScanAllTestSuite) TestCreateScanAllSchedule() {
|
||||
func (suite *ScanAllTestSuite) TestUpdateScanAllSchedule() {
|
||||
times := 11
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("IsSysAdmin").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
|
||||
|
||||
{
|
||||
@ -472,7 +470,7 @@ func (suite *ScanAllTestSuite) TestUpdateScanAllSchedule() {
|
||||
func (suite *ScanAllTestSuite) TestGetScanAllSchedule() {
|
||||
times := 4
|
||||
suite.Security.On("IsAuthenticated").Return(true).Times(times)
|
||||
suite.Security.On("IsSysAdmin").Return(true).Times(times)
|
||||
suite.Security.On("Can", mock.Anything, mock.Anything, mock.Anything).Return(true).Times(times)
|
||||
mock.OnAnything(suite.scannerCtl, "ListRegistrations").Return([]*scanner.Registration{{ID: int64(1)}}, nil).Times(times)
|
||||
|
||||
{
|
||||
|
@ -2,6 +2,7 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
@ -43,7 +44,7 @@ func (s *sysInfoAPI) GetSysteminfoGetcert(ctx context.Context, params systeminfo
|
||||
}
|
||||
|
||||
func (s *sysInfoAPI) GetSysteminfoVolumes(ctx context.Context, params systeminfo.GetSysteminfoVolumesParams) middleware.Responder {
|
||||
if err := s.RequireSysAdmin(ctx); err != nil {
|
||||
if err := s.RequireSystemAccess(ctx, rbac.ActionRead, rbac.ResourceSystemVolumes); err != nil {
|
||||
return s.SendError(ctx, err)
|
||||
}
|
||||
c, err := s.ctl.GetCapacity(ctx)
|
||||
|
Loading…
Reference in New Issue
Block a user