mirror of
https://github.com/goharbor/harbor.git
synced 2024-06-22 21:05:02 +02:00
parent 8e61a3ea31
author Wang Yan <wangyan@vmware.com> 1605849192 +0800 committer Wang Yan <wangyan@vmware.com> 1606361046 +0800 update code per review comments Signed-off-by: wang yan <wangyan@vmware.com>
This commit is contained in:
parent
8e61a3ea31
commit
02846194e0
|
@ -1990,187 +1990,6 @@ paths:
|
|||
$ref: '#/definitions/NotFoundChartAPIError'
|
||||
'500':
|
||||
$ref: '#/definitions/InternalChartAPIError'
|
||||
'/projects/{project_id}/robots':
|
||||
get:
|
||||
summary: Get all robot accounts of specified project
|
||||
description: Get all robot accounts of specified project
|
||||
parameters:
|
||||
- name: page
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The page number.
|
||||
- name: page_size
|
||||
in: query
|
||||
type: integer
|
||||
format: int32
|
||||
required: false
|
||||
description: The size of per page.
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
tags:
|
||||
- Products
|
||||
- Robot Account
|
||||
responses:
|
||||
'200':
|
||||
description: Get project robot accounts successfully.
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/RobotAccount'
|
||||
headers:
|
||||
X-Total-Count:
|
||||
description: The total count of available items
|
||||
type: integer
|
||||
Link:
|
||||
description: Link to previous page and next page
|
||||
type: string
|
||||
'400':
|
||||
description: The project id is invalid.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'404':
|
||||
description: Project ID does not exist.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
post:
|
||||
summary: Create a robot account for project
|
||||
description: Create a robot account for project
|
||||
tags:
|
||||
- Products
|
||||
- Robot Account
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: robot
|
||||
in: body
|
||||
description: Request body of creating a robot account.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/RobotAccountCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: Project member created successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/RobotAccountPostRep'
|
||||
headers:
|
||||
Location:
|
||||
type: string
|
||||
description: The URL of the created resource
|
||||
'400':
|
||||
description: Project id is not valid.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'409':
|
||||
description: An robot account with same name already exist in the project.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/projects/{project_id}/robots/{robot_id}':
|
||||
get:
|
||||
summary: Return the infor of the specified robot account.
|
||||
description: Return the infor of the specified robot account.
|
||||
tags:
|
||||
- Products
|
||||
- Robot Account
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: robot_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: The ID of robot account.
|
||||
responses:
|
||||
'200':
|
||||
description: Robot account information.
|
||||
schema:
|
||||
$ref: '#/definitions/RobotAccount'
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'404':
|
||||
description: The robot account is not found.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
put:
|
||||
summary: Update status of robot account.
|
||||
description: Used to disable/enable a specified robot account.
|
||||
tags:
|
||||
- Products
|
||||
- Robot Account
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: robot_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: The ID of robot account.
|
||||
- name: robot
|
||||
in: body
|
||||
description: Request body of enable/disable a robot account.
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/RobotAccountUpdate'
|
||||
responses:
|
||||
'200':
|
||||
description: Robot account has been modified success.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
delete:
|
||||
summary: Delete the specified robot account
|
||||
description: Delete the specified robot account
|
||||
tags:
|
||||
- Products
|
||||
- Robot Account
|
||||
parameters:
|
||||
- name: project_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: Relevant project ID.
|
||||
- name: robot_id
|
||||
in: path
|
||||
type: integer
|
||||
format: int64
|
||||
required: true
|
||||
description: The ID of robot account.
|
||||
responses:
|
||||
'200':
|
||||
description: The specified robot account is successfully deleted.
|
||||
'401':
|
||||
description: User need to log in first.
|
||||
'403':
|
||||
description: User in session does not have permission to the project.
|
||||
'404':
|
||||
description: The robot account is not found.
|
||||
'500':
|
||||
description: Unexpected internal errors.
|
||||
'/system/oidc/ping':
|
||||
post:
|
||||
summary: Test the OIDC endpoint.
|
||||
|
@ -4633,75 +4452,6 @@ definitions:
|
|||
error:
|
||||
type: string
|
||||
description: (optional) The error message when the status is "unhealthy"
|
||||
RobotAccount:
|
||||
type: object
|
||||
description: The object of robot account
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
description: The id of robot account
|
||||
name:
|
||||
type: string
|
||||
description: The name of robot account
|
||||
description:
|
||||
type: string
|
||||
description: The description of robot account
|
||||
expires_at:
|
||||
type: integer
|
||||
description: The expiration of robot account (in seconds)
|
||||
project_id:
|
||||
type: integer
|
||||
description: The project id of robot account
|
||||
disabled:
|
||||
type: boolean
|
||||
description: The robot account is disable or enable
|
||||
creation_time:
|
||||
type: string
|
||||
description: The creation time of the robot account
|
||||
update_time:
|
||||
type: string
|
||||
description: The update time of the robot account
|
||||
RobotAccountCreate:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: The name of robot account
|
||||
description:
|
||||
type: string
|
||||
description: The description of robot account
|
||||
expires_at:
|
||||
type: integer
|
||||
description: The expiration time on or after which the JWT MUST NOT be accepted for processing.
|
||||
access:
|
||||
type: array
|
||||
description: The permission of robot account
|
||||
items:
|
||||
$ref: '#/definitions/RobotAccountAccess'
|
||||
RobotAccountPostRep:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: the name of robot account
|
||||
token:
|
||||
type: string
|
||||
description: the token of robot account
|
||||
RobotAccountAccess:
|
||||
type: object
|
||||
properties:
|
||||
resource:
|
||||
type: string
|
||||
description: the resource of harbor
|
||||
action:
|
||||
type: string
|
||||
description: the action to resource that perdefined in harbor rbac
|
||||
RobotAccountUpdate:
|
||||
type: object
|
||||
properties:
|
||||
disabled:
|
||||
type: boolean
|
||||
description: The robot account is disable or enable
|
||||
Permission:
|
||||
type: object
|
||||
description: The permission
|
||||
|
|
|
@ -23,20 +23,21 @@ import (
|
|||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
)
|
||||
|
||||
// SecurityContext implements security.Context interface based on database
|
||||
type SecurityContext struct {
|
||||
robot *model.Robot
|
||||
ctl project.Controller
|
||||
policy []*types.Policy
|
||||
evaluator evaluator.Evaluator
|
||||
once sync.Once
|
||||
robot *model.Robot
|
||||
isSystemLevel bool
|
||||
ctl project.Controller
|
||||
policy []*types.Policy
|
||||
evaluator evaluator.Evaluator
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
// NewSecurityContext ...
|
||||
func NewSecurityContext(robot *model.Robot, policy []*types.Policy) *SecurityContext {
|
||||
func NewSecurityContext(robot *model.Robot, isSystemLevel bool, policy []*types.Policy) *SecurityContext {
|
||||
return &SecurityContext{
|
||||
ctl: project.Ctl,
|
||||
robot: robot,
|
||||
|
@ -76,7 +77,11 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
|||
// Can returns whether the robot can do action on resource
|
||||
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||
s.once.Do(func() {
|
||||
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy, filterRobotPolicies))
|
||||
if s.isSystemLevel {
|
||||
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy))
|
||||
} else {
|
||||
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy, filterRobotPolicies))
|
||||
}
|
||||
})
|
||||
|
||||
return s.evaluator != nil && s.evaluator.HasPermission(ctx, resource, action)
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
"github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -38,45 +38,45 @@ var (
|
|||
|
||||
func TestIsAuthenticated(t *testing.T) {
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, nil)
|
||||
ctx := NewSecurityContext(nil, false, nil)
|
||||
assert.False(t, ctx.IsAuthenticated())
|
||||
|
||||
// authenticated
|
||||
ctx = NewSecurityContext(&model.Robot{
|
||||
Name: "test",
|
||||
Disabled: false,
|
||||
}, nil)
|
||||
}, false, nil)
|
||||
assert.True(t, ctx.IsAuthenticated())
|
||||
}
|
||||
|
||||
func TestGetUsername(t *testing.T) {
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, nil)
|
||||
ctx := NewSecurityContext(nil, false, nil)
|
||||
assert.Equal(t, "", ctx.GetUsername())
|
||||
|
||||
// authenticated
|
||||
ctx = NewSecurityContext(&model.Robot{
|
||||
Name: "test",
|
||||
Disabled: false,
|
||||
}, nil)
|
||||
}, false, nil)
|
||||
assert.Equal(t, "test", ctx.GetUsername())
|
||||
}
|
||||
|
||||
func TestIsSysAdmin(t *testing.T) {
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, nil)
|
||||
ctx := NewSecurityContext(nil, false, nil)
|
||||
assert.False(t, ctx.IsSysAdmin())
|
||||
|
||||
// authenticated, non admin
|
||||
ctx = NewSecurityContext(&model.Robot{
|
||||
Name: "test",
|
||||
Disabled: false,
|
||||
}, nil)
|
||||
}, false, nil)
|
||||
assert.False(t, ctx.IsSysAdmin())
|
||||
}
|
||||
|
||||
func TestIsSolutionUser(t *testing.T) {
|
||||
ctx := NewSecurityContext(nil, nil)
|
||||
ctx := NewSecurityContext(nil, false, nil)
|
||||
assert.False(t, ctx.IsSolutionUser())
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ func TestHasPullPerm(t *testing.T) {
|
|||
ctl := &projecttesting.Controller{}
|
||||
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||
|
||||
ctx := NewSecurityContext(robot, policies)
|
||||
ctx := NewSecurityContext(robot, false, policies)
|
||||
ctx.ctl = ctl
|
||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||
|
@ -116,7 +116,7 @@ func TestHasPushPerm(t *testing.T) {
|
|||
ctl := &projecttesting.Controller{}
|
||||
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||
|
||||
ctx := NewSecurityContext(robot, policies)
|
||||
ctx := NewSecurityContext(robot, false, policies)
|
||||
ctx.ctl = ctl
|
||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
||||
|
@ -141,7 +141,7 @@ func TestHasPushPullPerm(t *testing.T) {
|
|||
ctl := &projecttesting.Controller{}
|
||||
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||
|
||||
ctx := NewSecurityContext(robot, policies)
|
||||
ctx := NewSecurityContext(robot, false, policies)
|
||||
ctx.ctl = ctl
|
||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource) && ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||
|
|
|
@ -105,7 +105,7 @@ func (d *controller) Create(ctx context.Context, r *Robot) (int64, error) {
|
|||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
r.ID = robotID
|
||||
if err := d.createPermission(ctx, r); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
@ -261,6 +261,7 @@ func (d *controller) populatePermissions(ctx context.Context, r *Robot) error {
|
|||
log.Errorf("failed to decode scope of robot %d: %v", r.ID, err)
|
||||
return err
|
||||
}
|
||||
p.Scope = scope
|
||||
p.Kind = kind
|
||||
p.Namespace = namespace
|
||||
p.Access = accesses
|
||||
|
|
|
@ -42,6 +42,7 @@ type Permission struct {
|
|||
Kind string `json:"kind"`
|
||||
Namespace string `json:"namespace"`
|
||||
Access []*types.Policy `json:"access"`
|
||||
Scope string `json:"-"`
|
||||
}
|
||||
|
||||
// Option ...
|
||||
|
|
|
@ -18,20 +18,21 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"sync"
|
||||
|
||||
cj "github.com/goharbor/harbor/src/common/job"
|
||||
jm "github.com/goharbor/harbor/src/common/job/models"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
ar "github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/controller/robot"
|
||||
sc "github.com/goharbor/harbor/src/controller/scanner"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
sca "github.com/goharbor/harbor/src/pkg/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
|
@ -70,6 +71,8 @@ type basicController struct {
|
|||
sc sc.Controller
|
||||
// Robot account controller
|
||||
rc robot.Controller
|
||||
// Project controller
|
||||
pro project.Controller
|
||||
// Job service client
|
||||
jc jcGetter
|
||||
// UUID generator
|
||||
|
@ -88,7 +91,9 @@ func NewController() Controller {
|
|||
// Refer to the default scanner controller
|
||||
sc: sc.DefaultController,
|
||||
// Refer to the default robot account controller
|
||||
rc: robot.RobotCtr,
|
||||
rc: robot.Ctl,
|
||||
// Refer to the default project controller
|
||||
pro: project.Ctl,
|
||||
// Refer to the default job service client
|
||||
jc: func() cj.Client {
|
||||
return cj.GlobalClient
|
||||
|
@ -282,7 +287,7 @@ func (bc *basicController) makeReportPlaceholder(ctx context.Context, r *scanner
|
|||
}
|
||||
|
||||
func (bc *basicController) scanArtifact(ctx context.Context, r *scanner.Registration, artifact *ar.Artifact, trackID string, producesMimes []string) error {
|
||||
jobID, err := bc.launchScanJob(trackID, artifact, r, producesMimes)
|
||||
jobID, err := bc.launchScanJob(ctx, trackID, artifact, r, producesMimes)
|
||||
if err != nil {
|
||||
// Update the status to the concrete error
|
||||
// Change status code to normal error code
|
||||
|
@ -494,7 +499,7 @@ func (bc *basicController) GetScanLog(uuid string) ([]byte, error) {
|
|||
}
|
||||
|
||||
// HandleJobHooks ...
|
||||
func (bc *basicController) HandleJobHooks(trackID string, change *job.StatusChange) error {
|
||||
func (bc *basicController) HandleJobHooks(ctx context.Context, trackID string, change *job.StatusChange) error {
|
||||
if len(trackID) == 0 {
|
||||
return errors.New("empty track ID")
|
||||
}
|
||||
|
@ -514,7 +519,7 @@ func (bc *basicController) HandleJobHooks(trackID string, change *job.StatusChan
|
|||
}
|
||||
|
||||
if r.ID > 0 {
|
||||
if err := robot.RobotCtr.DeleteRobotAccount(r.ID); err != nil {
|
||||
if err := robot.Ctl.Delete(ctx, r.ID); err != nil {
|
||||
// Should not block the main flow, just logged
|
||||
log.Error(errors.Wrap(err, "scan controller: handle job hook"))
|
||||
} else {
|
||||
|
@ -578,34 +583,59 @@ func (bc *basicController) GetStats(requester string) (*all.Stats, error) {
|
|||
}
|
||||
|
||||
// makeRobotAccount creates a robot account based on the arguments for scanning.
|
||||
func (bc *basicController) makeRobotAccount(projectID int64, repository string, registration *scanner.Registration) (*model.Robot, error) {
|
||||
func (bc *basicController) makeRobotAccount(ctx context.Context, projectID int64, repository string, registration *scanner.Registration) (*robot.Robot, error) {
|
||||
// Use uuid as name to avoid duplicated entries.
|
||||
UUID, err := bc.uuid()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "scan controller: make robot account")
|
||||
}
|
||||
|
||||
resource := rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceRepository)
|
||||
robotReq := &model.RobotCreate{
|
||||
Name: fmt.Sprintf("%s-%s", registration.Name, UUID),
|
||||
Description: "for scan",
|
||||
ProjectID: projectID,
|
||||
Access: []*types.Policy{
|
||||
{Resource: resource, Action: rbac.ActionPull},
|
||||
{Resource: resource, Action: rbac.ActionScannerPull},
|
||||
},
|
||||
}
|
||||
|
||||
rb, err := bc.rc.CreateRobotAccount(robotReq)
|
||||
p, err := bc.pro.Get(ctx, projectID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "scan controller: make robot account")
|
||||
}
|
||||
|
||||
return rb, nil
|
||||
robotReq := &robot.Robot{
|
||||
Robot: model.Robot{
|
||||
Name: fmt.Sprintf("%s-%s", registration.Name, UUID),
|
||||
Description: "for scan",
|
||||
ProjectID: projectID,
|
||||
ExpiresAt: -1,
|
||||
},
|
||||
Level: robot.LEVELPROJECT,
|
||||
Permissions: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: p.Name,
|
||||
Access: []*types.Policy{
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
Action: rbac.ActionPull,
|
||||
},
|
||||
{
|
||||
Resource: rbac.ResourceRepository,
|
||||
Action: rbac.ActionScannerPull,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
rb, err := bc.rc.Create(ctx, robotReq)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "scan controller: make robot account")
|
||||
}
|
||||
|
||||
r, err := bc.rc.Get(ctx, rb, &robot.Option{WithPermission: false})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "scan controller: make robot account")
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// launchScanJob launches a job to run scan
|
||||
func (bc *basicController) launchScanJob(trackID string, artifact *ar.Artifact, registration *scanner.Registration, mimes []string) (jobID string, err error) {
|
||||
func (bc *basicController) launchScanJob(ctx context.Context, trackID string, artifact *ar.Artifact, registration *scanner.Registration, mimes []string) (jobID string, err error) {
|
||||
var ck string
|
||||
if registration.UseInternalAddr {
|
||||
ck = configCoreInternalAddr
|
||||
|
@ -618,7 +648,7 @@ func (bc *basicController) launchScanJob(trackID string, artifact *ar.Artifact,
|
|||
return "", errors.Wrap(err, "scan controller: launch scan job")
|
||||
}
|
||||
|
||||
robot, err := bc.makeRobotAccount(artifact.ProjectID, artifact.RepositoryName, registration)
|
||||
robot, err := bc.makeRobotAccount(ctx, artifact.ProjectID, artifact.RepositoryName, registration)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "scan controller: launch scan job")
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ import (
|
|||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
models "github.com/goharbor/harbor/src/common/models"
|
||||
"github.com/goharbor/harbor/src/controller/robot"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -27,15 +29,16 @@ import (
|
|||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/controller/artifact"
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
sca "github.com/goharbor/harbor/src/pkg/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
|
||||
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||
robottesting "github.com/goharbor/harbor/src/testing/controller/robot"
|
||||
scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner"
|
||||
mocktesting "github.com/goharbor/harbor/src/testing/mock"
|
||||
reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report"
|
||||
|
@ -166,27 +169,48 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
|||
mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil)
|
||||
suite.reportMgr = mgr
|
||||
|
||||
rc := &MockRobotController{}
|
||||
|
||||
resource := fmt.Sprintf("/project/%d/repository", suite.artifact.ProjectID)
|
||||
access := []*types.Policy{
|
||||
{Resource: types.Resource(resource), Action: rbac.ActionPull},
|
||||
{Resource: types.Resource(resource), Action: rbac.ActionScannerPull},
|
||||
}
|
||||
rc := &robottesting.Controller{}
|
||||
|
||||
rname := fmt.Sprintf("%s-%s", suite.registration.Name, "the-uuid-123")
|
||||
account := &model.RobotCreate{
|
||||
Name: rname,
|
||||
Description: "for scan",
|
||||
ProjectID: suite.artifact.ProjectID,
|
||||
Access: access,
|
||||
|
||||
account := &robot.Robot{
|
||||
Robot: model.Robot{
|
||||
Name: rname,
|
||||
Description: "for scan",
|
||||
ProjectID: suite.artifact.ProjectID,
|
||||
ExpiresAt: -1,
|
||||
},
|
||||
Level: robot.LEVELPROJECT,
|
||||
Permissions: []*robot.Permission{
|
||||
{
|
||||
Kind: "project",
|
||||
Namespace: "library",
|
||||
Access: []*types.Policy{
|
||||
{
|
||||
Resource: "repository",
|
||||
Action: rbac.ActionPull,
|
||||
},
|
||||
{
|
||||
Resource: "repository",
|
||||
Action: rbac.ActionScannerPull,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rc.On("CreateRobotAccount", account).Return(&model.Robot{
|
||||
ID: 1,
|
||||
Name: rname,
|
||||
Token: "robot-account",
|
||||
Description: "for scan",
|
||||
ProjectID: suite.artifact.ProjectID,
|
||||
|
||||
rc.On("Create", context.TODO(), account).Return(int64(1), nil)
|
||||
rc.On("Get", context.TODO(), int64(1), &robot.Option{
|
||||
WithPermission: false,
|
||||
}).Return(&robot.Robot{
|
||||
Robot: model.Robot{
|
||||
ID: 1,
|
||||
Name: rname,
|
||||
Secret: "robot-account",
|
||||
Description: "for scan",
|
||||
ProjectID: suite.artifact.ProjectID,
|
||||
},
|
||||
Level: "project",
|
||||
}, nil)
|
||||
|
||||
// Set job parameters
|
||||
|
@ -208,7 +232,8 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
|||
regJSON, err := suite.registration.ToJSON()
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
rb, _ := rc.CreateRobotAccount(account)
|
||||
id, _ := rc.Create(context.TODO(), account)
|
||||
rb, _ := rc.Get(context.TODO(), id, &robot.Option{WithPermission: false})
|
||||
robotJSON, err := rb.ToJSON()
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
|
@ -237,6 +262,12 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
|||
walkFn(suite.artifact)
|
||||
})
|
||||
|
||||
proCtl := &projecttesting.Controller{}
|
||||
proCtl.On("Get", context.TODO(), suite.artifact.ProjectID).Return(&models.Project{
|
||||
ProjectID: suite.artifact.ProjectID,
|
||||
Name: "library",
|
||||
}, nil)
|
||||
|
||||
suite.c = &basicController{
|
||||
manager: mgr,
|
||||
ar: suite.ar,
|
||||
|
@ -244,7 +275,8 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
|||
jc: func() cj.Client {
|
||||
return jc
|
||||
},
|
||||
rc: rc,
|
||||
rc: rc,
|
||||
pro: proCtl,
|
||||
uuid: func() (string, error) {
|
||||
return "the-uuid-123", nil
|
||||
},
|
||||
|
@ -369,7 +401,7 @@ func (suite *ControllerTestSuite) TestScanControllerHandleJobHooks() {
|
|||
},
|
||||
}
|
||||
|
||||
err = suite.c.HandleJobHooks("the-uuid-123", statusChange)
|
||||
err = suite.c.HandleJobHooks(context.TODO(), "the-uuid-123", statusChange)
|
||||
require.NoError(suite.T(), err)
|
||||
}
|
||||
|
||||
|
@ -412,52 +444,3 @@ func (mjc *MockJobServiceClient) GetExecutions(uuid string) ([]job.Stats, error)
|
|||
|
||||
return args.Get(0).([]job.Stats), args.Error(1)
|
||||
}
|
||||
|
||||
// MockRobotController ...
|
||||
type MockRobotController struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetRobotAccount ...
|
||||
func (mrc *MockRobotController) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||
args := mrc.Called(id)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*model.Robot), args.Error(1)
|
||||
}
|
||||
|
||||
// CreateRobotAccount ...
|
||||
func (mrc *MockRobotController) CreateRobotAccount(robotReq *model.RobotCreate) (*model.Robot, error) {
|
||||
args := mrc.Called(robotReq)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).(*model.Robot), args.Error(1)
|
||||
}
|
||||
|
||||
// DeleteRobotAccount ...
|
||||
func (mrc *MockRobotController) DeleteRobotAccount(id int64) error {
|
||||
args := mrc.Called(id)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// UpdateRobotAccount ...
|
||||
func (mrc *MockRobotController) UpdateRobotAccount(r *model.Robot) error {
|
||||
args := mrc.Called(r)
|
||||
|
||||
return args.Error(0)
|
||||
}
|
||||
|
||||
// ListRobotAccount ...
|
||||
func (mrc *MockRobotController) ListRobotAccount(query *q.Query) ([]*model.Robot, error) {
|
||||
args := mrc.Called(query)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
|
||||
return args.Get(0).([]*model.Robot), args.Error(1)
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ type Controller interface {
|
|||
//
|
||||
// Returns:
|
||||
// error : non nil error if any errors occurred
|
||||
HandleJobHooks(trackID string, change *job.StatusChange) error
|
||||
HandleJobHooks(ctx context.Context, trackID string, change *job.StatusChange) error
|
||||
|
||||
// Delete the reports related with the specified digests
|
||||
//
|
||||
|
|
|
@ -133,9 +133,6 @@ func init() {
|
|||
beego.Router("/api/system/CVEAllowlist", &SysCVEAllowlistAPI{}, "get:Get;put:Put")
|
||||
beego.Router("/api/system/oidc/ping", &OIDCAPI{}, "post:Ping")
|
||||
|
||||
beego.Router("/api/projects/:pid([0-9]+)/robots/", &RobotAPI{}, "post:Post;get:List")
|
||||
beego.Router("/api/projects/:pid([0-9]+)/robots/:id([0-9]+)", &RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
||||
|
||||
beego.Router("/api/replication/adapters", &ReplicationAdapterAPI{}, "get:List")
|
||||
|
||||
beego.Router("/api/replication/policies", &ReplicationPolicyAPI{}, "get:List;post:Create")
|
||||
|
|
|
@ -1,237 +0,0 @@
|
|||
// Copyright 2018 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 api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"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/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
)
|
||||
|
||||
// RobotAPI ...
|
||||
type RobotAPI struct {
|
||||
BaseController
|
||||
project *models.Project
|
||||
ctr robot.Controller
|
||||
robot *model.Robot
|
||||
}
|
||||
|
||||
// Prepare ...
|
||||
func (r *RobotAPI) Prepare() {
|
||||
|
||||
r.BaseController.Prepare()
|
||||
|
||||
if !r.SecurityCtx.IsAuthenticated() {
|
||||
r.SendUnAuthorizedError(errors.New("UnAuthorized"))
|
||||
return
|
||||
}
|
||||
|
||||
pid, err := r.GetInt64FromPath(":pid")
|
||||
if err != nil || pid <= 0 {
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = "failed to get project ID " + err.Error()
|
||||
} else {
|
||||
errMsg = "invalid project ID: " + fmt.Sprintf("%d", pid)
|
||||
}
|
||||
r.SendBadRequestError(errors.New(errMsg))
|
||||
return
|
||||
}
|
||||
project, err := r.ProjectCtl.Get(r.Context(), pid)
|
||||
if err != nil {
|
||||
if errors.IsNotFoundErr(err) {
|
||||
r.SendNotFoundError(fmt.Errorf("project %d not found", pid))
|
||||
} else {
|
||||
r.ParseAndHandleError(fmt.Sprintf("failed to get project %d", pid), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
r.project = project
|
||||
r.ctr = robot.RobotCtr
|
||||
|
||||
if r.ParamExistsInPath(":id") {
|
||||
id, err := r.GetInt64FromPath(":id")
|
||||
if err != nil || id <= 0 {
|
||||
r.SendBadRequestError(fmt.Errorf("invalid robot ID %s", r.GetStringFromPath(":id")))
|
||||
return
|
||||
}
|
||||
robot, err := r.ctr.GetRobotAccount(id)
|
||||
if err != nil {
|
||||
r.SendInternalServerError(fmt.Errorf("failed to get robot %d: %v", id, err))
|
||||
return
|
||||
}
|
||||
|
||||
if robot == nil {
|
||||
r.SendNotFoundError(fmt.Errorf("robot %d not found", id))
|
||||
return
|
||||
}
|
||||
|
||||
if robot.ProjectID != pid {
|
||||
r.SendNotFoundError(fmt.Errorf("robot %d not found in project %d", id, pid))
|
||||
return
|
||||
}
|
||||
|
||||
r.robot = robot
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RobotAPI) requireAccess(action rbac.Action) bool {
|
||||
return r.RequireProjectAccess(r.project.ProjectID, action, rbac.ResourceRobot)
|
||||
}
|
||||
|
||||
// Post ...
|
||||
func (r *RobotAPI) Post() {
|
||||
if !r.requireAccess(rbac.ActionCreate) {
|
||||
return
|
||||
}
|
||||
|
||||
var robotReq model.RobotCreate
|
||||
isValid, err := r.DecodeJSONReqAndValidate(&robotReq)
|
||||
if !isValid {
|
||||
r.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
robotReq.Visible = true
|
||||
robotReq.ProjectID = r.project.ProjectID
|
||||
|
||||
if err := validateRobotReq(r.project, &robotReq); err != nil {
|
||||
r.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
robot, err := r.ctr.CreateRobotAccount(&robotReq)
|
||||
if err != nil {
|
||||
if err == dao.ErrDupRows {
|
||||
r.SendConflictError(errors.New("conflict robot account"))
|
||||
return
|
||||
}
|
||||
r.SendInternalServerError(errors.Wrap(err, "robot API: post"))
|
||||
return
|
||||
}
|
||||
|
||||
w := r.Ctx.ResponseWriter
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
robotRep := model.RobotRep{
|
||||
Name: robot.Name,
|
||||
Token: robot.Token,
|
||||
}
|
||||
|
||||
r.Redirect(http.StatusCreated, strconv.FormatInt(robot.ID, 10))
|
||||
r.Data["json"] = robotRep
|
||||
r.ServeJSON()
|
||||
}
|
||||
|
||||
// List list all the robots of a project
|
||||
func (r *RobotAPI) List() {
|
||||
if !r.requireAccess(rbac.ActionList) {
|
||||
return
|
||||
}
|
||||
|
||||
keywords := make(map[string]interface{})
|
||||
keywords["ProjectID"] = r.project.ProjectID
|
||||
keywords["Visible"] = true
|
||||
query := &q.Query{
|
||||
Keywords: keywords,
|
||||
}
|
||||
robots, err := r.ctr.ListRobotAccount(query)
|
||||
if err != nil {
|
||||
r.SendInternalServerError(errors.Wrap(err, "robot API: list"))
|
||||
return
|
||||
}
|
||||
count := len(robots)
|
||||
page, size, err := r.GetPaginationParams()
|
||||
if err != nil {
|
||||
r.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r.SetPaginationHeader(int64(count), page, size)
|
||||
r.Data["json"] = robots
|
||||
r.ServeJSON()
|
||||
}
|
||||
|
||||
// Get get robot by id
|
||||
func (r *RobotAPI) Get() {
|
||||
if !r.requireAccess(rbac.ActionRead) {
|
||||
return
|
||||
}
|
||||
|
||||
r.Data["json"] = r.robot
|
||||
r.ServeJSON()
|
||||
}
|
||||
|
||||
// Put disable or enable a robot account
|
||||
func (r *RobotAPI) Put() {
|
||||
if !r.requireAccess(rbac.ActionUpdate) {
|
||||
return
|
||||
}
|
||||
|
||||
var robotReq model.RobotCreate
|
||||
if err := r.DecodeJSONReq(&robotReq); err != nil {
|
||||
r.SendBadRequestError(err)
|
||||
return
|
||||
}
|
||||
|
||||
r.robot.Disabled = robotReq.Disabled
|
||||
|
||||
if err := r.ctr.UpdateRobotAccount(r.robot); err != nil {
|
||||
r.SendInternalServerError(errors.Wrap(err, "robot API: update"))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Delete delete robot by id
|
||||
func (r *RobotAPI) Delete() {
|
||||
if !r.requireAccess(rbac.ActionDelete) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := r.ctr.DeleteRobotAccount(r.robot.ID); err != nil {
|
||||
r.SendInternalServerError(errors.Wrap(err, "robot API: delete"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateRobotReq(p *models.Project, robotReq *model.RobotCreate) error {
|
||||
if len(robotReq.Access) == 0 {
|
||||
return errors.New("access required")
|
||||
}
|
||||
|
||||
policies := rbac.GetPoliciesOfProject(p.ProjectID)
|
||||
|
||||
mp := map[string]bool{}
|
||||
for _, policy := range policies {
|
||||
mp[policy.String()] = true
|
||||
}
|
||||
|
||||
for _, policy := range robotReq.Access {
|
||||
if !mp[policy.String()] {
|
||||
return fmt.Errorf("%s action of %s resource not exist in project %s", policy.Action, policy.Resource, p.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,459 +0,0 @@
|
|||
// Copyright 2018 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 api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
)
|
||||
|
||||
var (
|
||||
robotPath = "/api/projects/1/robots"
|
||||
robotID int64
|
||||
)
|
||||
|
||||
func TestRobotAPIPost(t *testing.T) {
|
||||
res := types.Resource("/project/1")
|
||||
|
||||
rbacPolicy := &types.Policy{
|
||||
Resource: res.Subresource(rbac.ResourceRepository),
|
||||
Action: "pull",
|
||||
}
|
||||
policies := []*types.Policy{}
|
||||
policies = append(policies, rbacPolicy)
|
||||
|
||||
tokenDuration := time.Duration(30) * time.Minute
|
||||
expiresAt := time.Now().UTC().Add(tokenDuration).Unix()
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
|
||||
// 403
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{},
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
// 201
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "test",
|
||||
Description: "test desc",
|
||||
ExpiresAt: expiresAt,
|
||||
Access: policies,
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusCreated,
|
||||
},
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "testIllgel#",
|
||||
Description: "test desc",
|
||||
ExpiresAt: expiresAt,
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "test not set expiration",
|
||||
Description: "test desc",
|
||||
ExpiresAt: -100,
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "test",
|
||||
Description: "resource not exist",
|
||||
ExpiresAt: expiresAt,
|
||||
Access: []*types.Policy{
|
||||
{Resource: res.Subresource("foo"), Action: rbac.ActionCreate},
|
||||
},
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "test",
|
||||
Description: "action not exist",
|
||||
ExpiresAt: expiresAt,
|
||||
Access: []*types.Policy{
|
||||
{Resource: res.Subresource(rbac.ResourceRepository), Action: "foo"},
|
||||
},
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "test",
|
||||
Description: "policy not exit",
|
||||
ExpiresAt: expiresAt,
|
||||
Access: []*types.Policy{
|
||||
{Resource: res.Subresource(rbac.ResourceMember), Action: rbac.ActionPush},
|
||||
},
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
// 403 -- developer
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "test2",
|
||||
Description: "test2 desc",
|
||||
ExpiresAt: expiresAt,
|
||||
},
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
|
||||
// 409
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPost,
|
||||
url: robotPath,
|
||||
bodyJSON: &model.RobotCreate{
|
||||
Name: "test",
|
||||
Description: "test desc",
|
||||
ExpiresAt: expiresAt,
|
||||
Access: policies,
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusConflict,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestRobotAPIGet(t *testing.T) {
|
||||
projectID, err := dao.AddProject(models.Project{Name: "robotget", OwnerID: 1})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when add project: %v", err)
|
||||
}
|
||||
defer dao.DeleteProject(projectID)
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 0),
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1000),
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
|
||||
// 404 robot 1 not belong to the project
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("/api/projects/%d/robots/1", projectID),
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestRobotAPIList(t *testing.T) {
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: robotPath,
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: "/api/projects/0/robots",
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: robotPath,
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodGet,
|
||||
url: robotPath,
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestRobotAPIPut(t *testing.T) {
|
||||
projectID, err := dao.AddProject(models.Project{Name: "robotput", OwnerID: 1})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when add project: %v", err)
|
||||
}
|
||||
defer dao.DeleteProject(projectID)
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 0),
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 10000),
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
|
||||
// 404 robot 1 not belong to the project
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("/api/projects/%d/robots/1", projectID),
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
|
||||
// 403 non-member user
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
|
||||
// 403 developer
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodPut,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
bodyJSON: &model.Robot{
|
||||
Disabled: true,
|
||||
},
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
||||
|
||||
func TestRobotAPIDelete(t *testing.T) {
|
||||
projectID, err := dao.AddProject(models.Project{Name: "robotdelete", OwnerID: 1})
|
||||
if err != nil {
|
||||
t.Errorf("Error occurred when add project: %v", err)
|
||||
}
|
||||
defer dao.DeleteProject(projectID)
|
||||
|
||||
cases := []*codeCheckingCase{
|
||||
// 401
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
},
|
||||
code: http.StatusUnauthorized,
|
||||
},
|
||||
|
||||
// 400
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 0),
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusBadRequest,
|
||||
},
|
||||
|
||||
// 404
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 10000),
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
|
||||
// 404 robot 1 not belong to the project
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("/api/projects/%d/robots/1", projectID),
|
||||
credential: sysAdmin,
|
||||
},
|
||||
code: http.StatusNotFound,
|
||||
},
|
||||
|
||||
// 403 non-member user
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
credential: nonSysAdmin,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
|
||||
// 403 developer
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
credential: projDeveloper,
|
||||
},
|
||||
code: http.StatusForbidden,
|
||||
},
|
||||
|
||||
// 200
|
||||
{
|
||||
request: &testingRequest{
|
||||
method: http.MethodDelete,
|
||||
url: fmt.Sprintf("%s/%d", robotPath, 1),
|
||||
credential: projAdmin4Robot,
|
||||
},
|
||||
code: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
runCodeCheckingCases(t, cases...)
|
||||
}
|
|
@ -16,6 +16,7 @@ package jobs
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/lib/orm"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/job"
|
||||
|
@ -129,7 +130,7 @@ func (h *Handler) HandleScan() {
|
|||
}
|
||||
}
|
||||
|
||||
if err := scan.DefaultController.HandleJobHooks(h.trackID, h.change); err != nil {
|
||||
if err := scan.DefaultController.HandleJobHooks(orm.Context(), h.trackID, h.change); err != nil {
|
||||
err = errors.Wrap(err, "scan job hook handler")
|
||||
log.Error(err)
|
||||
h.SendInternalServerError(err)
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
package robot
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/token"
|
||||
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// RobotCtr is a global variable for the default robot account controller implementation
|
||||
RobotCtr = NewController(NewDefaultRobotAccountManager())
|
||||
)
|
||||
|
||||
// Controller to handle the requests related with robot account
|
||||
type Controller interface {
|
||||
// GetRobotAccount ...
|
||||
GetRobotAccount(id int64) (*model.Robot, error)
|
||||
|
||||
// CreateRobotAccount ...
|
||||
CreateRobotAccount(robotReq *model.RobotCreate) (*model.Robot, error)
|
||||
|
||||
// DeleteRobotAccount ...
|
||||
DeleteRobotAccount(id int64) error
|
||||
|
||||
// UpdateRobotAccount ...
|
||||
UpdateRobotAccount(r *model.Robot) error
|
||||
|
||||
// ListRobotAccount ...
|
||||
ListRobotAccount(query *q.Query) ([]*model.Robot, error)
|
||||
}
|
||||
|
||||
// DefaultAPIController ...
|
||||
type DefaultAPIController struct {
|
||||
manager Manager
|
||||
}
|
||||
|
||||
// NewController ...
|
||||
func NewController(robotMgr Manager) Controller {
|
||||
return &DefaultAPIController{
|
||||
manager: robotMgr,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRobotAccount ...
|
||||
func (d *DefaultAPIController) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||
return d.manager.GetRobotAccount(id)
|
||||
}
|
||||
|
||||
// CreateRobotAccount ...
|
||||
func (d *DefaultAPIController) CreateRobotAccount(robotReq *model.RobotCreate) (*model.Robot, error) {
|
||||
|
||||
var deferDel error
|
||||
createdName := common.RobotPrefix + robotReq.Name
|
||||
|
||||
if robotReq.ExpiresAt == 0 {
|
||||
tokenDuration := time.Duration(config.RobotTokenDuration()) * time.Minute
|
||||
robotReq.ExpiresAt = time.Now().UTC().Add(tokenDuration).Unix()
|
||||
}
|
||||
|
||||
// first to add a robot account, and get its id.
|
||||
robot := &model.Robot{
|
||||
Name: createdName,
|
||||
Description: robotReq.Description,
|
||||
ProjectID: robotReq.ProjectID,
|
||||
ExpiresAt: robotReq.ExpiresAt,
|
||||
Visible: robotReq.Visible,
|
||||
}
|
||||
id, err := d.manager.CreateRobotAccount(robot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// generate the token, and return it with response data.
|
||||
// token is not stored in the database.
|
||||
opt := token.DefaultTokenOptions()
|
||||
rClaims := &robot_claim.Claim{
|
||||
TokenID: id,
|
||||
ProjectID: robotReq.ProjectID,
|
||||
Access: robotReq.Access,
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
IssuedAt: time.Now().UTC().Unix(),
|
||||
Issuer: opt.Issuer,
|
||||
},
|
||||
}
|
||||
// "-1" means the robot account is a permanent account, no expiration time set.
|
||||
// The ExpiresAt claim is optional, so if it's not set, it will still be considered a valid claim
|
||||
if robot.ExpiresAt != -1 {
|
||||
rClaims.ExpiresAt = robotReq.ExpiresAt
|
||||
}
|
||||
tk, err := token.New(opt, rClaims)
|
||||
if err != nil {
|
||||
deferDel = err
|
||||
return nil, fmt.Errorf("failed to valid parameters to generate token for robot account, %v", err)
|
||||
}
|
||||
rawTk, err := tk.Raw()
|
||||
if err != nil {
|
||||
deferDel = err
|
||||
return nil, fmt.Errorf("failed to sign token for robot account, %v", err)
|
||||
}
|
||||
|
||||
defer func(deferDel error) {
|
||||
if deferDel != nil {
|
||||
if err := d.manager.DeleteRobotAccount(id); err != nil {
|
||||
log.Error(errors.Wrap(err, fmt.Sprintf("failed to delete the robot account: %d", id)))
|
||||
}
|
||||
}
|
||||
}(deferDel)
|
||||
|
||||
robot.Token = rawTk
|
||||
robot.ID = id
|
||||
return robot, nil
|
||||
}
|
||||
|
||||
// DeleteRobotAccount ...
|
||||
func (d *DefaultAPIController) DeleteRobotAccount(id int64) error {
|
||||
return d.manager.DeleteRobotAccount(id)
|
||||
}
|
||||
|
||||
// UpdateRobotAccount ...
|
||||
func (d *DefaultAPIController) UpdateRobotAccount(r *model.Robot) error {
|
||||
return d.manager.UpdateRobotAccount(r)
|
||||
}
|
||||
|
||||
// ListRobotAccount ...
|
||||
func (d *DefaultAPIController) ListRobotAccount(query *q.Query) ([]*model.Robot, error) {
|
||||
return d.manager.ListRobotAccount(query)
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
package robot
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
core_cfg "github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
)
|
||||
|
||||
type ControllerTestSuite struct {
|
||||
suite.Suite
|
||||
ctr Controller
|
||||
t *testing.T
|
||||
assert *assert.Assertions
|
||||
require *require.Assertions
|
||||
|
||||
robotID int64
|
||||
}
|
||||
|
||||
// SetupSuite ...
|
||||
func (s *ControllerTestSuite) SetupSuite() {
|
||||
test.InitDatabaseFromEnv()
|
||||
conf := map[string]interface{}{
|
||||
common.RobotTokenDuration: "30",
|
||||
}
|
||||
core_cfg.InitWithSettings(conf)
|
||||
s.t = s.T()
|
||||
s.assert = assert.New(s.t)
|
||||
s.require = require.New(s.t)
|
||||
s.ctr = RobotCtr
|
||||
}
|
||||
|
||||
func (s *ControllerTestSuite) TestRobotAccount() {
|
||||
|
||||
res := rbac.Resource("/project/1")
|
||||
|
||||
rbacPolicy := &types.Policy{
|
||||
Resource: res.Subresource(rbac.ResourceRepository),
|
||||
Action: "pull",
|
||||
}
|
||||
policies := []*types.Policy{}
|
||||
policies = append(policies, rbacPolicy)
|
||||
|
||||
tokenDuration := time.Duration(30) * time.Minute
|
||||
expiresAt := time.Now().UTC().Add(tokenDuration).Unix()
|
||||
|
||||
robot1 := &model.RobotCreate{
|
||||
Name: "robot1",
|
||||
Description: "TestCreateRobotAccount",
|
||||
ExpiresAt: expiresAt,
|
||||
ProjectID: int64(1),
|
||||
Access: policies,
|
||||
}
|
||||
|
||||
robot, err := s.ctr.CreateRobotAccount(robot1)
|
||||
s.require.Nil(err)
|
||||
s.require.Equal(robot.ProjectID, int64(1))
|
||||
s.require.Equal(robot.Description, "TestCreateRobotAccount")
|
||||
s.require.NotEmpty(robot.Token)
|
||||
s.require.Equal(robot.Name, common.RobotPrefix+"robot1")
|
||||
|
||||
robotGet, err := s.ctr.GetRobotAccount(robot.ID)
|
||||
s.require.Nil(err)
|
||||
s.require.Equal(robotGet.ProjectID, int64(1))
|
||||
s.require.Equal(robotGet.Description, "TestCreateRobotAccount")
|
||||
|
||||
robot.Disabled = true
|
||||
err = s.ctr.UpdateRobotAccount(robot)
|
||||
s.require.Nil(err)
|
||||
s.require.Equal(robot.Disabled, true)
|
||||
|
||||
robot2 := &model.RobotCreate{
|
||||
Name: "robot2",
|
||||
Description: "TestCreateRobotAccount",
|
||||
ExpiresAt: -1,
|
||||
ProjectID: int64(1),
|
||||
Access: policies,
|
||||
}
|
||||
r2, _ := s.ctr.CreateRobotAccount(robot2)
|
||||
s.robotID = r2.ID
|
||||
|
||||
robot3 := &model.RobotCreate{
|
||||
Name: "robot3",
|
||||
Description: "TestCreateRobotAccount",
|
||||
ExpiresAt: expiresAt,
|
||||
ProjectID: int64(11),
|
||||
Access: policies,
|
||||
}
|
||||
r3, _ := s.ctr.CreateRobotAccount(robot3)
|
||||
|
||||
keywords := make(map[string]interface{})
|
||||
keywords["ProjectID"] = int64(1)
|
||||
query := &q.Query{
|
||||
Keywords: keywords,
|
||||
}
|
||||
robots, err := s.ctr.ListRobotAccount(query)
|
||||
s.require.Nil(err)
|
||||
s.require.Equal(len(robots), 2)
|
||||
s.require.Equal(robots[1].Name, common.RobotPrefix+"robot2")
|
||||
|
||||
err = s.ctr.DeleteRobotAccount(robot.ID)
|
||||
s.require.Nil(err)
|
||||
err = s.ctr.DeleteRobotAccount(r3.ID)
|
||||
s.require.Nil(err)
|
||||
|
||||
robots, err = s.ctr.ListRobotAccount(query)
|
||||
s.require.Equal(len(robots), 1)
|
||||
}
|
||||
|
||||
// TearDownSuite clears env for test suite
|
||||
func (s *ControllerTestSuite) TearDownSuite() {
|
||||
err := s.ctr.DeleteRobotAccount(s.robotID)
|
||||
require.NoError(s.T(), err, "delete robot")
|
||||
}
|
||||
|
||||
// TestController ...
|
||||
func TestController(t *testing.T) {
|
||||
suite.Run(t, new(ControllerTestSuite))
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
libOrm "github.com/goharbor/harbor/src/lib/orm"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// RobotAccountDao defines the interface to access the ImmutableRule data model
|
||||
type RobotAccountDao interface {
|
||||
// CreateRobotAccount ...
|
||||
CreateRobotAccount(robot *model.Robot) (int64, error)
|
||||
|
||||
// UpdateRobotAccount ...
|
||||
UpdateRobotAccount(robot *model.Robot) error
|
||||
|
||||
// GetRobotAccount ...
|
||||
GetRobotAccount(id int64) (*model.Robot, error)
|
||||
|
||||
// ListRobotAccounts ...
|
||||
ListRobotAccounts(query *q.Query) ([]*model.Robot, error)
|
||||
|
||||
// DeleteRobotAccount ...
|
||||
DeleteRobotAccount(id int64) error
|
||||
|
||||
// DeleteByProjectID ...
|
||||
DeleteByProjectID(ctx context.Context, projectID int64) error
|
||||
}
|
||||
|
||||
// New creates a default implementation for RobotAccountDao
|
||||
func New() RobotAccountDao {
|
||||
return &robotAccountDao{}
|
||||
}
|
||||
|
||||
type robotAccountDao struct{}
|
||||
|
||||
// CreateRobotAccount ...
|
||||
func (r *robotAccountDao) CreateRobotAccount(robot *model.Robot) (int64, error) {
|
||||
now := time.Now()
|
||||
robot.CreationTime = now
|
||||
robot.UpdateTime = now
|
||||
id, err := dao.GetOrmer().Insert(robot)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "duplicate key value violates unique constraint") {
|
||||
return 0, dao.ErrDupRows
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
// GetRobotAccount ...
|
||||
func (r *robotAccountDao) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||
robot := &model.Robot{
|
||||
ID: id,
|
||||
}
|
||||
if err := dao.GetOrmer().Read(robot); err != nil {
|
||||
if err == orm.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return robot, nil
|
||||
}
|
||||
|
||||
// ListRobotAccounts ...
|
||||
func (r *robotAccountDao) ListRobotAccounts(query *q.Query) ([]*model.Robot, error) {
|
||||
o := dao.GetOrmer()
|
||||
qt := o.QueryTable(new(model.Robot))
|
||||
|
||||
if query != nil {
|
||||
if len(query.Keywords) > 0 {
|
||||
for k, v := range query.Keywords {
|
||||
if k == "ProjectID" {
|
||||
qt = qt.Filter("ProjectID", v)
|
||||
} else {
|
||||
qt = qt.Filter(fmt.Sprintf("%s__icontains", k), v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if query.PageNumber > 0 && query.PageSize > 0 {
|
||||
qt = qt.Limit(query.PageSize, (query.PageNumber-1)*query.PageSize)
|
||||
}
|
||||
}
|
||||
|
||||
robots := make([]*model.Robot, 0)
|
||||
_, err := qt.All(&robots)
|
||||
return robots, err
|
||||
}
|
||||
|
||||
// UpdateRobotAccount ...
|
||||
func (r *robotAccountDao) UpdateRobotAccount(robot *model.Robot) error {
|
||||
robot.UpdateTime = time.Now()
|
||||
_, err := dao.GetOrmer().Update(robot)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteRobotAccount ...
|
||||
func (r *robotAccountDao) DeleteRobotAccount(id int64) error {
|
||||
_, err := dao.GetOrmer().QueryTable(&model.Robot{}).Filter("ID", id).Delete()
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteByProjectID ...
|
||||
func (r *robotAccountDao) DeleteByProjectID(ctx context.Context, projectID int64) error {
|
||||
qs, err := libOrm.QuerySetter(ctx, &model.Robot{}, q.New(q.KeyWords{"ProjectID": projectID}))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = qs.Delete()
|
||||
return err
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
package dao
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common/dao"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
htesting "github.com/goharbor/harbor/src/testing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type robotAccountDaoTestSuite struct {
|
||||
htesting.Suite
|
||||
require *require.Assertions
|
||||
assert *assert.Assertions
|
||||
dao RobotAccountDao
|
||||
id1 int64
|
||||
id2 int64
|
||||
id3 int64
|
||||
id4 int64
|
||||
}
|
||||
|
||||
func (t *robotAccountDaoTestSuite) SetupSuite() {
|
||||
t.require = require.New(t.T())
|
||||
t.assert = assert.New(t.T())
|
||||
dao.PrepareTestForPostgresSQL()
|
||||
t.dao = New()
|
||||
}
|
||||
|
||||
func (t *robotAccountDaoTestSuite) TestCreateRobotAccount() {
|
||||
robotName := "test1"
|
||||
robot := &model.Robot{
|
||||
Name: robotName,
|
||||
Description: "test1 description",
|
||||
ProjectID: 1,
|
||||
}
|
||||
id, err := t.dao.CreateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
t.id1 = id
|
||||
t.require.Nil(err)
|
||||
t.require.NotNil(id)
|
||||
}
|
||||
|
||||
func (t *robotAccountDaoTestSuite) TestGetRobotAccount() {
|
||||
robotName := "test2"
|
||||
robot := &model.Robot{
|
||||
Name: robotName,
|
||||
Description: "test2 description",
|
||||
ProjectID: 1,
|
||||
}
|
||||
|
||||
// add
|
||||
id, err := t.dao.CreateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
t.id2 = id
|
||||
|
||||
robot, err = t.dao.GetRobotAccount(id)
|
||||
t.require.Nil(err)
|
||||
t.require.Equal(robotName, robot.Name)
|
||||
}
|
||||
|
||||
func (t *robotAccountDaoTestSuite) TestListRobotAccounts() {
|
||||
robotName := "test3"
|
||||
robot := &model.Robot{
|
||||
Name: robotName,
|
||||
Description: "test3 description",
|
||||
ProjectID: 1,
|
||||
}
|
||||
|
||||
id, err := t.dao.CreateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
t.id3 = id
|
||||
|
||||
keywords := make(map[string]interface{})
|
||||
keywords["ProjectID"] = 1
|
||||
robots, err := t.dao.ListRobotAccounts(&q.Query{
|
||||
Keywords: keywords,
|
||||
})
|
||||
t.require.Nil(err)
|
||||
t.require.Equal(3, len(robots))
|
||||
}
|
||||
|
||||
func (t *robotAccountDaoTestSuite) TestUpdateRobotAccount() {
|
||||
robotName := "test4"
|
||||
robot := &model.Robot{
|
||||
Name: robotName,
|
||||
Description: "test4 description",
|
||||
ProjectID: 1,
|
||||
}
|
||||
// add
|
||||
id, err := t.dao.CreateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
t.id4 = id
|
||||
// Disable
|
||||
robot.Disabled = true
|
||||
err = t.dao.UpdateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
// Get
|
||||
robot, err = t.dao.GetRobotAccount(id)
|
||||
t.require.Nil(err)
|
||||
t.require.Equal(true, robot.Disabled)
|
||||
}
|
||||
|
||||
func (t *robotAccountDaoTestSuite) TestDeleteRobotAccount() {
|
||||
robotName := "test5"
|
||||
robot := &model.Robot{
|
||||
Name: robotName,
|
||||
Description: "test5 description",
|
||||
ProjectID: 1,
|
||||
}
|
||||
// add
|
||||
id, err := t.dao.CreateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
// Disable
|
||||
err = t.dao.DeleteRobotAccount(id)
|
||||
t.require.Nil(err)
|
||||
// Get
|
||||
robot, err = t.dao.GetRobotAccount(id)
|
||||
t.require.Nil(err)
|
||||
}
|
||||
|
||||
func (t *robotAccountDaoTestSuite) TestDeleteRobotAccountByPID() {
|
||||
t.WithProject(func(projectID int64, projectName string) {
|
||||
robot := &model.Robot{
|
||||
Name: t.RandString(5),
|
||||
Description: "TestDeleteRobotAccountByPID description",
|
||||
ProjectID: projectID,
|
||||
}
|
||||
_, err := t.dao.CreateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
robot = &model.Robot{
|
||||
Name: t.RandString(5),
|
||||
Description: "TestDeleteRobotAccountByPID description",
|
||||
ProjectID: projectID,
|
||||
}
|
||||
_, err = t.dao.CreateRobotAccount(robot)
|
||||
t.require.Nil(err)
|
||||
|
||||
// Delete
|
||||
err = t.dao.DeleteByProjectID(t.Context(), projectID)
|
||||
t.require.Nil(err)
|
||||
|
||||
// Get
|
||||
keywords := make(map[string]interface{})
|
||||
keywords["ProjectID"] = projectID
|
||||
robots, err := t.dao.ListRobotAccounts(&q.Query{
|
||||
Keywords: keywords,
|
||||
})
|
||||
t.require.Nil(err)
|
||||
t.require.Equal(0, len(robots))
|
||||
})
|
||||
}
|
||||
|
||||
// TearDownSuite clears env for test suite
|
||||
func (t *robotAccountDaoTestSuite) TearDownSuite() {
|
||||
err := t.dao.DeleteRobotAccount(t.id1)
|
||||
require.NoError(t.T(), err, "delete robot 1")
|
||||
|
||||
err = t.dao.DeleteRobotAccount(t.id2)
|
||||
require.NoError(t.T(), err, "delete robot 2")
|
||||
|
||||
err = t.dao.DeleteRobotAccount(t.id3)
|
||||
require.NoError(t.T(), err, "delete robot 3")
|
||||
|
||||
err = t.dao.DeleteRobotAccount(t.id4)
|
||||
require.NoError(t.T(), err, "delete robot 4")
|
||||
}
|
||||
|
||||
func TestRobotAccountDaoTestSuite(t *testing.T) {
|
||||
suite.Run(t, &robotAccountDaoTestSuite{})
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
package robot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/dao"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
)
|
||||
|
||||
var (
|
||||
// Mgr is a global variable for the default robot account manager implementation
|
||||
Mgr = NewDefaultRobotAccountManager()
|
||||
)
|
||||
|
||||
// Manager ...
|
||||
type Manager interface {
|
||||
// GetRobotAccount ...
|
||||
GetRobotAccount(id int64) (*model.Robot, error)
|
||||
|
||||
// CreateRobotAccount ...
|
||||
CreateRobotAccount(m *model.Robot) (int64, error)
|
||||
|
||||
// DeleteRobotAccount ...
|
||||
DeleteRobotAccount(id int64) error
|
||||
|
||||
// DeleteByProjectID ...
|
||||
DeleteByProjectID(ctx context.Context, projectID int64) error
|
||||
|
||||
// UpdateRobotAccount ...
|
||||
UpdateRobotAccount(m *model.Robot) error
|
||||
|
||||
// ListRobotAccount ...
|
||||
ListRobotAccount(query *q.Query) ([]*model.Robot, error)
|
||||
}
|
||||
|
||||
type defaultRobotManager struct {
|
||||
dao dao.RobotAccountDao
|
||||
}
|
||||
|
||||
// NewDefaultRobotAccountManager return a new instance of defaultRobotManager
|
||||
func NewDefaultRobotAccountManager() Manager {
|
||||
return &defaultRobotManager{
|
||||
dao: dao.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// GetRobotAccount ...
|
||||
func (drm *defaultRobotManager) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||
return drm.dao.GetRobotAccount(id)
|
||||
}
|
||||
|
||||
// CreateRobotAccount ...
|
||||
func (drm *defaultRobotManager) CreateRobotAccount(r *model.Robot) (int64, error) {
|
||||
return drm.dao.CreateRobotAccount(r)
|
||||
}
|
||||
|
||||
// DeleteRobotAccount ...
|
||||
func (drm *defaultRobotManager) DeleteRobotAccount(id int64) error {
|
||||
return drm.dao.DeleteRobotAccount(id)
|
||||
}
|
||||
|
||||
// DeleteByProjectID ...
|
||||
func (drm *defaultRobotManager) DeleteByProjectID(ctx context.Context, projectID int64) error {
|
||||
return drm.dao.DeleteByProjectID(ctx, projectID)
|
||||
}
|
||||
|
||||
// UpdateRobotAccount ...
|
||||
func (drm *defaultRobotManager) UpdateRobotAccount(r *model.Robot) error {
|
||||
return drm.dao.UpdateRobotAccount(r)
|
||||
}
|
||||
|
||||
// ListRobotAccount ...
|
||||
func (drm *defaultRobotManager) ListRobotAccount(query *q.Query) ([]*model.Robot, error) {
|
||||
return drm.dao.ListRobotAccounts(query)
|
||||
}
|
|
@ -1,112 +0,0 @@
|
|||
package robot
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/testing/mock"
|
||||
"github.com/goharbor/harbor/src/testing/pkg/robot/dao"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type managerTestingSuite struct {
|
||||
suite.Suite
|
||||
t *testing.T
|
||||
assert *assert.Assertions
|
||||
require *require.Assertions
|
||||
mockRobotDao *dao.RobotAccountDao
|
||||
mgr Manager
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) SetupSuite() {
|
||||
m.t = m.T()
|
||||
m.assert = assert.New(m.t)
|
||||
m.require = require.New(m.t)
|
||||
|
||||
err := os.Setenv("RUN_MODE", "TEST")
|
||||
m.require.Nil(err)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TearDownSuite() {
|
||||
err := os.Unsetenv("RUN_MODE")
|
||||
m.require.Nil(err)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) SetupTest() {
|
||||
m.mockRobotDao = &dao.RobotAccountDao{}
|
||||
m.mgr = &defaultRobotManager{
|
||||
dao: m.mockRobotDao,
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagerTestingSuite(t *testing.T) {
|
||||
suite.Run(t, &managerTestingSuite{})
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TestCreateRobotAccount() {
|
||||
m.mockRobotDao.On("CreateRobotAccount", mock.Anything, mock.Anything).Return(int64(1), nil)
|
||||
id, err := m.mgr.CreateRobotAccount(&model.Robot{})
|
||||
m.mockRobotDao.AssertCalled(m.t, "CreateRobotAccount", mock.Anything)
|
||||
m.require.Nil(err)
|
||||
m.assert.Equal(int64(1), id)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TestUpdateRobotAccount() {
|
||||
m.mockRobotDao.On("UpdateRobotAccount", mock.Anything, mock.Anything).Return(nil)
|
||||
err := m.mgr.UpdateRobotAccount(&model.Robot{})
|
||||
m.mockRobotDao.AssertCalled(m.t, "UpdateRobotAccount", mock.Anything)
|
||||
m.require.Nil(err)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TestDeleteRobotAccount() {
|
||||
m.mockRobotDao.On("DeleteRobotAccount", mock.Anything, mock.Anything).Return(nil)
|
||||
err := m.mgr.DeleteRobotAccount(int64(1))
|
||||
m.mockRobotDao.AssertCalled(m.t, "DeleteRobotAccount", mock.Anything)
|
||||
m.require.Nil(err)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) TestGetRobotAccount() {
|
||||
m.mockRobotDao.On("GetRobotAccount", mock.Anything, mock.Anything).Return(&model.Robot{
|
||||
ID: 1,
|
||||
ProjectID: 1,
|
||||
Disabled: true,
|
||||
ExpiresAt: 150000,
|
||||
}, nil)
|
||||
ir, err := m.mgr.GetRobotAccount(1)
|
||||
m.mockRobotDao.AssertCalled(m.t, "GetRobotAccount", mock.Anything, mock.Anything)
|
||||
m.require.Nil(err)
|
||||
m.require.NotNil(ir)
|
||||
m.assert.Equal(int64(1), ir.ID)
|
||||
}
|
||||
|
||||
func (m *managerTestingSuite) ListRobotAccount() {
|
||||
m.mockRobotDao.On("ListRobotAccount", mock.Anything, mock.Anything).Return([]model.Robot{
|
||||
{
|
||||
ID: 1,
|
||||
ProjectID: 1,
|
||||
Disabled: false,
|
||||
ExpiresAt: 12345,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
ProjectID: 1,
|
||||
Disabled: false,
|
||||
ExpiresAt: 54321,
|
||||
}}, nil)
|
||||
|
||||
keywords := make(map[string]interface{})
|
||||
keywords["ProjectID"] = int64(1)
|
||||
query := &q.Query{
|
||||
Keywords: keywords,
|
||||
}
|
||||
rs, err := m.mgr.ListRobotAccount(query)
|
||||
m.mockRobotDao.AssertCalled(m.t, "ListRobotAccount", mock.Anything, mock.Anything)
|
||||
m.require.Nil(err)
|
||||
m.assert.Equal(len(rs), 2)
|
||||
m.assert.Equal(rs[0].Disabled, false)
|
||||
m.assert.Equal(rs[1].ExpiresAt, 54321)
|
||||
|
||||
}
|
|
@ -1,102 +0,0 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
"github.com/astaxie/beego/validation"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
)
|
||||
|
||||
// RobotTable is the name of table in DB that holds the robot object
|
||||
const RobotTable = "robot"
|
||||
|
||||
func init() {
|
||||
orm.RegisterModel(&Robot{})
|
||||
}
|
||||
|
||||
// Robot holds the details of a robot.
|
||||
type Robot struct {
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"id"`
|
||||
Name string `orm:"column(name)" json:"name"`
|
||||
Token string `orm:"-" json:"token"`
|
||||
Description string `orm:"column(description)" json:"description"`
|
||||
ProjectID int64 `orm:"column(project_id)" json:"project_id"`
|
||||
ExpiresAt int64 `orm:"column(expiresat)" json:"expires_at"`
|
||||
Disabled bool `orm:"column(disabled)" json:"disabled"`
|
||||
Visible bool `orm:"column(visible)" json:"-"`
|
||||
CreationTime time.Time `orm:"column(creation_time);auto_now_add" json:"creation_time"`
|
||||
UpdateTime time.Time `orm:"column(update_time);auto_now" json:"update_time"`
|
||||
}
|
||||
|
||||
// TableName ...
|
||||
func (r *Robot) TableName() string {
|
||||
return RobotTable
|
||||
}
|
||||
|
||||
// FromJSON parses robot from json data
|
||||
func (r *Robot) FromJSON(jsonData string) error {
|
||||
if len(jsonData) == 0 {
|
||||
return errors.New("empty json data to parse")
|
||||
}
|
||||
|
||||
return json.Unmarshal([]byte(jsonData), r)
|
||||
}
|
||||
|
||||
// ToJSON marshals Robot to JSON data
|
||||
func (r *Robot) ToJSON() (string, error) {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// RobotQuery ...
|
||||
type RobotQuery struct {
|
||||
Name string
|
||||
ProjectID int64
|
||||
Disabled bool
|
||||
FuzzyMatchName bool
|
||||
Pagination
|
||||
}
|
||||
|
||||
// RobotCreate ...
|
||||
type RobotCreate struct {
|
||||
Name string `json:"name"`
|
||||
ProjectID int64 `json:"pid"`
|
||||
Description string `json:"description"`
|
||||
Disabled bool `json:"disabled"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
Visible bool `json:"-"`
|
||||
Access []*types.Policy `json:"access"`
|
||||
}
|
||||
|
||||
// Pagination ...
|
||||
type Pagination struct {
|
||||
Page int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
// Valid ...
|
||||
func (rq *RobotCreate) Valid(v *validation.Validation) {
|
||||
if utils.IsIllegalLength(rq.Name, 1, 255) {
|
||||
v.SetError("name", "robot name with illegal length")
|
||||
}
|
||||
if utils.IsContainIllegalChar(rq.Name, []string{",", "~", "#", "$", "%"}) {
|
||||
v.SetError("name", "robot name contains illegal characters")
|
||||
}
|
||||
if rq.ExpiresAt < -1 {
|
||||
v.SetError("expires_at", "expiration time must be a positive integer or -1 if set")
|
||||
}
|
||||
}
|
||||
|
||||
// RobotRep ...
|
||||
type RobotRep struct {
|
||||
Name string `json:"name"`
|
||||
Token string `json:"token"`
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"time"
|
||||
|
||||
"github.com/astaxie/beego/orm"
|
||||
|
@ -28,3 +30,22 @@ type Robot struct {
|
|||
func (r *Robot) TableName() string {
|
||||
return "robot"
|
||||
}
|
||||
|
||||
// FromJSON parses robot from json data
|
||||
func (r *Robot) FromJSON(jsonData string) error {
|
||||
if len(jsonData) == 0 {
|
||||
return errors.New("empty json data to parse")
|
||||
}
|
||||
|
||||
return json.Unmarshal([]byte(jsonData), r)
|
||||
}
|
||||
|
||||
// ToJSON marshals Robot to JSON data
|
||||
func (r *Robot) ToJSON() (string, error) {
|
||||
data, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import (
|
|||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
|
@ -460,7 +460,7 @@ func getInternalTokenServiceEndpoint(ctx job.Context) (string, error) {
|
|||
|
||||
// makeBasicAuthorization creates authorization from a robot account based on the arguments for scanning.
|
||||
func makeBasicAuthorization(robotAccount *model.Robot) (string, error) {
|
||||
basic := fmt.Sprintf("%s:%s", robotAccount.Name, robotAccount.Token)
|
||||
basic := fmt.Sprintf("%s:%s", robotAccount.Name, robotAccount.Secret)
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(basic))
|
||||
|
||||
return fmt.Sprintf("Basic %s", encoded), nil
|
||||
|
|
|
@ -16,11 +16,12 @@ package scan
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/goharbor/harbor/src/controller/robot"
|
||||
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/jobservice/job"
|
||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||
|
@ -90,10 +91,13 @@ func (suite *JobTestSuite) TestJob() {
|
|||
sData, err := sr.ToJSON()
|
||||
require.NoError(suite.T(), err)
|
||||
|
||||
robot := &model.Robot{
|
||||
ID: 1,
|
||||
Name: "robot",
|
||||
Token: "token",
|
||||
robot := &robot.Robot{
|
||||
Robot: model.Robot{
|
||||
ID: 1,
|
||||
Name: "robot",
|
||||
Secret: "token",
|
||||
},
|
||||
Level: "project",
|
||||
}
|
||||
|
||||
robotData, err := robot.ToJSON()
|
||||
|
|
|
@ -21,8 +21,8 @@ import (
|
|||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
||||
ctl_robot "github.com/goharbor/harbor/src/controller/robot"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
pkgrobot "github.com/goharbor/harbor/src/pkg/robot"
|
||||
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
||||
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
||||
)
|
||||
|
@ -46,8 +46,8 @@ func (r *robot) Generate(req *http.Request) security.Context {
|
|||
return nil
|
||||
}
|
||||
// Do authn for robot account, as Harbor only stores the token ID, just validate the ID and disable.
|
||||
ctr := pkgrobot.RobotCtr
|
||||
robot, err := ctr.GetRobotAccount(rtk.Claims.(*robot_claim.Claim).TokenID)
|
||||
ctr := ctl_robot.Ctl
|
||||
robot, err := ctr.Get(req.Context(), rtk.Claims.(*robot_claim.Claim).TokenID, nil)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get robot %s: %v", robotName, err)
|
||||
return nil
|
||||
|
@ -65,5 +65,6 @@ func (r *robot) Generate(req *http.Request) security.Context {
|
|||
return nil
|
||||
}
|
||||
log.Debugf("a robot security context generated for request %s %s", req.Method, req.URL.Path)
|
||||
return robotCtx.NewSecurityContext(robot, rtk.Claims.(*robot_claim.Claim).Access)
|
||||
|
||||
return robotCtx.NewSecurityContext(&robot.Robot, false, rtk.Claims.(*robot_claim.Claim).Access)
|
||||
}
|
||||
|
|
82
src/server/middleware/security/robot2.go
Normal file
82
src/server/middleware/security/robot2.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
package security
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/goharbor/harbor/src/common/security"
|
||||
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
robot_ctl "github.com/goharbor/harbor/src/controller/robot"
|
||||
"github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
|
||||
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type robot2 struct{}
|
||||
|
||||
func (r *robot2) Generate(req *http.Request) security.Context {
|
||||
log := log.G(req.Context())
|
||||
name, secret, ok := req.BasicAuth()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if !strings.HasPrefix(name, config.RobotPrefix()) {
|
||||
return nil
|
||||
}
|
||||
key, err := config.SecretKey()
|
||||
if err != nil {
|
||||
log.Error("failed to get secret key")
|
||||
return nil
|
||||
}
|
||||
_, err = utils.ReversibleDecrypt(secret, key)
|
||||
if err != nil {
|
||||
log.Errorf("failed to decode secret key: %s, %v", secret, err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO use the naming pattern to avoid the permission boundary crossing.
|
||||
robots, err := robot_ctl.Ctl.List(req.Context(), q.New(q.KeyWords{
|
||||
"name": strings.TrimPrefix(name, config.RobotPrefix()),
|
||||
}), &robot_ctl.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to list robots: %v", err)
|
||||
return nil
|
||||
}
|
||||
if len(robots) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var accesses []*types.Policy
|
||||
robot := robots[0]
|
||||
if secret != robot.Secret {
|
||||
log.Error("the secret provided is not correct.")
|
||||
return nil
|
||||
}
|
||||
if robot.Disabled {
|
||||
log.Error("the robot is disabled.")
|
||||
return nil
|
||||
}
|
||||
// add the expiration check
|
||||
|
||||
for _, p := range robot.Permissions {
|
||||
for _, a := range p.Access {
|
||||
accesses = append(accesses, &types.Policy{
|
||||
Action: a.Action,
|
||||
Effect: a.Effect,
|
||||
Resource: types.Resource(fmt.Sprintf("%s/%s", p.Scope, a.Resource)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
modelRobot := &model.Robot{
|
||||
Name: strings.TrimPrefix(name, config.RobotPrefix()),
|
||||
}
|
||||
log.Infof("a robot2 security context generated for request %s %s", req.Method, req.URL.Path)
|
||||
return robotCtx.NewSecurityContext(modelRobot, robot.Level == robot_ctl.LEVELSYSTEM, accesses)
|
||||
}
|
32
src/server/middleware/security/robot2_test.go
Normal file
32
src/server/middleware/security/robot2_test.go
Normal file
|
@ -0,0 +1,32 @@
|
|||
package security
|
||||
|
||||
import (
|
||||
"github.com/goharbor/harbor/src/common"
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
core_cfg "github.com/goharbor/harbor/src/core/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRobot2(t *testing.T) {
|
||||
secretKeyPath := "/tmp/secretkey"
|
||||
_, err := test.GenerateKey(secretKeyPath)
|
||||
assert.Nil(t, err)
|
||||
defer os.Remove(secretKeyPath)
|
||||
os.Setenv("KEY_PATH", secretKeyPath)
|
||||
|
||||
conf := map[string]interface{}{
|
||||
common.RobotPrefix: "robot@",
|
||||
}
|
||||
core_cfg.InitWithSettings(conf)
|
||||
|
||||
robot := &robot2{}
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/api/projects/", nil)
|
||||
require.Nil(t, err)
|
||||
req.SetBasicAuth("robot@est1", "Harbor12345")
|
||||
ctx := robot.Generate(req)
|
||||
assert.Nil(t, ctx)
|
||||
}
|
|
@ -32,6 +32,7 @@ var (
|
|||
&idToken{},
|
||||
&authProxy{},
|
||||
&robot{},
|
||||
&robot2{},
|
||||
&basicAuth{},
|
||||
&session{},
|
||||
&proxyCacheSecret{},
|
||||
|
|
|
@ -7,10 +7,12 @@ import (
|
|||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
)
|
||||
|
||||
// Robot ...
|
||||
type Robot struct {
|
||||
*robot.Robot
|
||||
}
|
||||
|
||||
// ToSwagger ...
|
||||
func (r *Robot) ToSwagger() *models.Robot {
|
||||
perms := []*models.Permission{}
|
||||
for _, p := range r.Permissions {
|
||||
|
|
|
@ -120,7 +120,6 @@ func (rAPI *robotAPI) ListRobot(ctx context.Context, params operation.ListRobotP
|
|||
|
||||
} else {
|
||||
level = robot.LEVELSYSTEM
|
||||
projectID = 0
|
||||
query.Keywords["ProjectID"] = 0
|
||||
}
|
||||
|
||||
|
@ -239,21 +238,13 @@ func (rAPI *robotAPI) validate(r *models.RobotCreate) error {
|
|||
return errors.New(nil).WithMessage("bad request empty permission").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
|
||||
if r.Level == robot.LEVELPROJECT {
|
||||
// to create a project robot, the permission must be only one project scope.
|
||||
if len(r.Permissions) > 1 {
|
||||
return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
// to create a project robot, the permission must be only one project scope.
|
||||
if r.Level == robot.LEVELPROJECT && len(r.Permissions) > 1 {
|
||||
return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isValidLevel(l string) bool {
|
||||
switch l {
|
||||
case
|
||||
robot.LEVELSYSTEM,
|
||||
robot.LEVELPROJECT:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
return l == robot.LEVELSYSTEM || l == robot.LEVELPROJECT
|
||||
}
|
||||
|
|
282
src/server/v2.0/handler/robotV1.go
Normal file
282
src/server/v2.0/handler/robotV1.go
Normal file
|
@ -0,0 +1,282 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/goharbor/harbor/src/common/rbac"
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/controller/project"
|
||||
"github.com/goharbor/harbor/src/controller/robot"
|
||||
"github.com/goharbor/harbor/src/lib"
|
||||
"github.com/goharbor/harbor/src/lib/errors"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/lib/q"
|
||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||
pkg_robot "github.com/goharbor/harbor/src/pkg/robot2"
|
||||
pkg "github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||
handler_model "github.com/goharbor/harbor/src/server/v2.0/handler/model"
|
||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||
operation "github.com/goharbor/harbor/src/server/v2.0/restapi/operations/robotv1"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func newRobotV1API() *robotV1API {
|
||||
return &robotV1API{
|
||||
robotCtl: robot.Ctl,
|
||||
robotMgr: pkg_robot.Mgr,
|
||||
projectCtr: project.Ctl,
|
||||
}
|
||||
}
|
||||
|
||||
type robotV1API struct {
|
||||
BaseAPI
|
||||
robotCtl robot.Controller
|
||||
robotMgr pkg_robot.Manager
|
||||
projectCtr project.Controller
|
||||
}
|
||||
|
||||
func (rAPI *robotV1API) CreateRobotV1(ctx context.Context, params operation.CreateRobotV1Params) middleware.Responder {
|
||||
if err := rAPI.RequireProjectAccess(ctx, params.ProjectIDOrName, rbac.ActionCreate, rbac.ResourceRobot); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if err := rAPI.validate(ctx, params); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
r := &robot.Robot{
|
||||
Robot: pkg.Robot{
|
||||
Name: params.Robot.Name,
|
||||
Description: params.Robot.Description,
|
||||
ExpiresAt: params.Robot.ExpiresAt,
|
||||
},
|
||||
Level: robot.LEVELPROJECT,
|
||||
}
|
||||
|
||||
projectID, projectName, err := utils.ParseProjectIDOrName(params.ProjectIDOrName)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
if projectID != 0 {
|
||||
p, err := project.Ctl.Get(ctx, projectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %s: %v", projectName, err)
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
if p == nil {
|
||||
log.Warningf("project %s not found", projectName)
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
projectName = p.Name
|
||||
}
|
||||
|
||||
permission := &robot.Permission{
|
||||
Kind: "project",
|
||||
Namespace: projectName,
|
||||
}
|
||||
|
||||
var policies []*types.Policy
|
||||
for _, acc := range params.Robot.Access {
|
||||
policy := &types.Policy{
|
||||
Action: types.Action(acc.Action),
|
||||
Effect: types.Effect(acc.Effect),
|
||||
}
|
||||
res, err := getRawResource(acc.Resource)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
policy.Resource = types.Resource(res)
|
||||
policies = append(policies, policy)
|
||||
}
|
||||
permission.Access = policies
|
||||
r.Permissions = append(r.Permissions, permission)
|
||||
|
||||
rid, err := rAPI.robotCtl.Create(ctx, r)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
created, err := rAPI.robotCtl.Get(ctx, rid, nil)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
location := fmt.Sprintf("%s/%d", strings.TrimSuffix(params.HTTPRequest.URL.Path, "/"), created.ID)
|
||||
return operation.NewCreateRobotV1Created().WithLocation(location).WithPayload(&models.RobotCreated{
|
||||
ID: created.ID,
|
||||
Name: created.Name,
|
||||
Secret: created.Secret,
|
||||
CreationTime: strfmt.DateTime(created.CreationTime),
|
||||
})
|
||||
}
|
||||
|
||||
func (rAPI *robotV1API) DeleteRobotV1(ctx context.Context, params operation.DeleteRobotV1Params) middleware.Responder {
|
||||
if err := rAPI.RequireProjectAccess(ctx, params.ProjectIDOrName, rbac.ActionDelete, rbac.ResourceRobot); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
pro, err := rAPI.projectCtr.Get(ctx, params.ProjectIDOrName)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
r, err := rAPI.robotCtl.List(ctx, q.New(q.KeyWords{"ProjectID": pro.ProjectID, "ID": params.RobotID}), &robot.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
return rAPI.SendError(ctx, errors.NotFoundError(fmt.Errorf("cannot find robot with project id: %d and id: %d", pro.ProjectID, params.RobotID)))
|
||||
}
|
||||
|
||||
// ignore the not permissions error.
|
||||
if err := rAPI.robotCtl.Delete(ctx, params.RobotID); err != nil && errors.IsNotFoundErr(err) {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
return operation.NewDeleteRobotV1OK()
|
||||
}
|
||||
|
||||
func (rAPI *robotV1API) ListRobotV1(ctx context.Context, params operation.ListRobotV1Params) middleware.Responder {
|
||||
if err := rAPI.RequireProjectAccess(ctx, params.ProjectIDOrName, rbac.ActionList, rbac.ResourceRobot); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query, err := rAPI.BuildQuery(ctx, params.Q, params.Page, params.PageSize)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
pro, err := rAPI.projectCtr.Get(ctx, params.ProjectIDOrName)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
query.Keywords["ProjectID"] = pro.ProjectID
|
||||
|
||||
total, err := rAPI.robotCtl.Count(ctx, query)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
robots, err := rAPI.robotCtl.List(ctx, query, &robot.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
var results []*models.Robot
|
||||
for _, r := range robots {
|
||||
results = append(results, handler_model.NewRobot(r).ToSwagger())
|
||||
}
|
||||
|
||||
return operation.NewListRobotV1OK().
|
||||
WithXTotalCount(total).
|
||||
WithLink(rAPI.Links(ctx, params.HTTPRequest.URL, total, query.PageNumber, query.PageSize).String()).
|
||||
WithPayload(results)
|
||||
}
|
||||
|
||||
func (rAPI *robotV1API) GetRobotByIDV1(ctx context.Context, params operation.GetRobotByIDV1Params) middleware.Responder {
|
||||
if err := rAPI.RequireProjectAccess(ctx, params.ProjectIDOrName, rbac.ActionRead, rbac.ResourceRobot); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
pro, err := rAPI.projectCtr.Get(ctx, params.ProjectIDOrName)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
r, err := rAPI.robotCtl.List(ctx, q.New(q.KeyWords{"ProjectID": pro.ProjectID, "ID": params.RobotID}), &robot.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
return rAPI.SendError(ctx, errors.NotFoundError(fmt.Errorf("cannot find robot with project id: %d and id: %d", pro.ProjectID, params.RobotID)))
|
||||
}
|
||||
|
||||
return operation.NewGetRobotByIDV1OK().WithPayload(handler_model.NewRobot(r[0]).ToSwagger())
|
||||
}
|
||||
|
||||
func (rAPI *robotV1API) UpdateRobotV1(ctx context.Context, params operation.UpdateRobotV1Params) middleware.Responder {
|
||||
if err := rAPI.RequireProjectAccess(ctx, params.ProjectIDOrName, rbac.ActionUpdate, rbac.ResourceRobot); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
|
||||
pro, err := rAPI.projectCtr.Get(ctx, params.ProjectIDOrName)
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
r, err := rAPI.robotCtl.List(ctx, q.New(q.KeyWords{"ProjectID": pro.ProjectID, "ID": params.RobotID}), &robot.Option{
|
||||
WithPermission: true,
|
||||
})
|
||||
if err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
if len(r) == 0 {
|
||||
return rAPI.SendError(ctx, errors.NotFoundError(fmt.Errorf("cannot find robot with project id: %d and id: %d", pro.ProjectID, params.RobotID)))
|
||||
}
|
||||
robot := r[0]
|
||||
|
||||
// for v1 API, only update the name and description.
|
||||
if robot.Disabled != params.Robot.Disable {
|
||||
robot.Robot.Disabled = params.Robot.Disable
|
||||
if err := rAPI.robotMgr.Update(ctx, &robot.Robot); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
}
|
||||
if robot.Description != params.Robot.Description {
|
||||
robot.Robot.Description = params.Robot.Description
|
||||
if err := rAPI.robotMgr.Update(ctx, &robot.Robot); err != nil {
|
||||
return rAPI.SendError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
return operation.NewUpdateRobotV1OK()
|
||||
}
|
||||
|
||||
func (rAPI *robotV1API) validate(ctx context.Context, params operation.CreateRobotV1Params) error {
|
||||
if params.Robot == nil {
|
||||
return errors.New(nil).WithMessage("bad request no robot").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
if len(params.Robot.Access) == 0 {
|
||||
return errors.New(nil).WithMessage("bad request no access").WithCode(errors.BadRequestCode)
|
||||
}
|
||||
|
||||
pro, err := rAPI.projectCtr.Get(ctx, params.ProjectIDOrName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
policies := rbac.GetPoliciesOfProject(pro.ProjectID)
|
||||
|
||||
mp := map[string]bool{}
|
||||
for _, policy := range policies {
|
||||
mp[policy.String()] = true
|
||||
}
|
||||
|
||||
for _, policy := range params.Robot.Access {
|
||||
p := &types.Policy{}
|
||||
lib.JSONCopy(p, policy)
|
||||
if !mp[p.String()] {
|
||||
return errors.New(nil).WithMessage("%s action of %s resource not exist in project %s", policy.Action, policy.Resource, params.ProjectIDOrName).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// /project/1/repository => repository
|
||||
func getRawResource(resource string) (string, error) {
|
||||
resourceReg := regexp.MustCompile("^/project/[0-9]+/(?P<repository>[a-z-]+)$")
|
||||
matches := resourceReg.FindStringSubmatch(resource)
|
||||
if len(matches) <= 1 {
|
||||
return "", errors.New(nil).WithMessage("bad resource %s", resource).WithCode(errors.BadRequestCode)
|
||||
}
|
||||
return matches[1], nil
|
||||
}
|
|
@ -42,8 +42,6 @@ func registerLegacyRoutes() {
|
|||
beego.Router("/api/"+version+"/search", &api.SearchAPI{})
|
||||
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/?:name", &api.MetadataAPI{}, "get:Get")
|
||||
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/robots", &api.RobotAPI{}, "post:Post;get:List")
|
||||
beego.Router("/api/"+version+"/projects/:pid([0-9]+)/robots/:id([0-9]+)", &api.RobotAPI{}, "get:Get;put:Put;delete:Delete")
|
||||
|
||||
beego.Router("/api/"+version+"/quotas", &api.QuotaAPI{}, "get:List")
|
||||
beego.Router("/api/"+version+"/quotas/:id([0-9]+)", &api.QuotaAPI{}, "get:Get;put:Put")
|
||||
|
|
|
@ -22,3 +22,4 @@ package controller
|
|||
//go:generate mockery --case snake --dir ../../controller/scan --name Checker --output ./scan --outpkg scan
|
||||
//go:generate mockery --case snake --dir ../../controller/scanner --name Controller --output ./scanner --outpkg scanner
|
||||
//go:generate mockery --case snake --dir ../../controller/replication --name Controller --output ./replication --outpkg replication
|
||||
//go:generate mockery --case snake --dir ../../controller/robot --name Controller --output ./robot --outpkg robot
|
||||
|
|
133
src/testing/controller/robot/controller.go
Normal file
133
src/testing/controller/robot/controller.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package robot
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
robot "github.com/goharbor/harbor/src/controller/robot"
|
||||
)
|
||||
|
||||
// Controller is an autogenerated mock type for the Controller type
|
||||
type Controller struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Count provides a mock function with given fields: ctx, query
|
||||
func (_m *Controller) Count(ctx context.Context, query *q.Query) (int64, error) {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query) int64); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *q.Query) error); ok {
|
||||
r1 = rf(ctx, query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Create provides a mock function with given fields: ctx, r
|
||||
func (_m *Controller) Create(ctx context.Context, r *robot.Robot) (int64, error) {
|
||||
ret := _m.Called(ctx, r)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *robot.Robot) int64); ok {
|
||||
r0 = rf(ctx, r)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *robot.Robot) error); ok {
|
||||
r1 = rf(ctx, r)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Delete provides a mock function with given fields: ctx, id
|
||||
func (_m *Controller) Delete(ctx context.Context, id int64) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Get provides a mock function with given fields: ctx, id, option
|
||||
func (_m *Controller) Get(ctx context.Context, id int64, option *robot.Option) (*robot.Robot, error) {
|
||||
ret := _m.Called(ctx, id, option)
|
||||
|
||||
var r0 *robot.Robot
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64, *robot.Option) *robot.Robot); ok {
|
||||
r0 = rf(ctx, id, option)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*robot.Robot)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int64, *robot.Option) error); ok {
|
||||
r1 = rf(ctx, id, option)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// List provides a mock function with given fields: ctx, query, option
|
||||
func (_m *Controller) List(ctx context.Context, query *q.Query, option *robot.Option) ([]*robot.Robot, error) {
|
||||
ret := _m.Called(ctx, query, option)
|
||||
|
||||
var r0 []*robot.Robot
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *q.Query, *robot.Option) []*robot.Robot); ok {
|
||||
r0 = rf(ctx, query, option)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*robot.Robot)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *q.Query, *robot.Option) error); ok {
|
||||
r1 = rf(ctx, query, option)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: ctx, r
|
||||
func (_m *Controller) Update(ctx context.Context, r *robot.Robot) error {
|
||||
ret := _m.Called(ctx, r)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *robot.Robot) error); ok {
|
||||
r0 = rf(ctx, r)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
|
@ -143,13 +143,13 @@ func (_m *Controller) GetSummary(ctx context.Context, _a1 *artifact.Artifact, mi
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// HandleJobHooks provides a mock function with given fields: trackID, change
|
||||
func (_m *Controller) HandleJobHooks(trackID string, change *job.StatusChange) error {
|
||||
ret := _m.Called(trackID, change)
|
||||
// HandleJobHooks provides a mock function with given fields: ctx, trackID, change
|
||||
func (_m *Controller) HandleJobHooks(ctx context.Context, trackID string, change *job.StatusChange) error {
|
||||
ret := _m.Called(ctx, trackID, change)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, *job.StatusChange) error); ok {
|
||||
r0 = rf(trackID, change)
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *job.StatusChange) error); ok {
|
||||
r0 = rf(ctx, trackID, change)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
|
|
@ -28,7 +28,6 @@ package pkg
|
|||
//go:generate mockery --case snake --dir ../../pkg/task --name Manager --output ./task --outpkg task
|
||||
//go:generate mockery --case snake --dir ../../pkg/task --name ExecutionManager --output ./task --outpkg task
|
||||
//go:generate mockery --case snake --dir ../../pkg/user --name Manager --output ./user --outpkg user
|
||||
//go:generate mockery --case snake --dir ../../pkg/robot/dao --name RobotAccountDao --output ./robot/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/rbac --name Manager --output ./rbac --outpkg rbac
|
||||
//go:generate mockery --case snake --dir ../../pkg/rbac/dao --name DAO --output ./rbac/dao --outpkg dao
|
||||
//go:generate mockery --case snake --dir ../../pkg/robot2 --name Manager --output ./robot2 --outpkg robot2
|
||||
|
|
|
@ -1,127 +0,0 @@
|
|||
// Code generated by mockery v2.1.0. DO NOT EDIT.
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
|
||||
model "github.com/goharbor/harbor/src/pkg/robot/model"
|
||||
|
||||
q "github.com/goharbor/harbor/src/lib/q"
|
||||
)
|
||||
|
||||
// RobotAccountDao is an autogenerated mock type for the RobotAccountDao type
|
||||
type RobotAccountDao struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// CreateRobotAccount provides a mock function with given fields: robot
|
||||
func (_m *RobotAccountDao) CreateRobotAccount(robot *model.Robot) (int64, error) {
|
||||
ret := _m.Called(robot)
|
||||
|
||||
var r0 int64
|
||||
if rf, ok := ret.Get(0).(func(*model.Robot) int64); ok {
|
||||
r0 = rf(robot)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*model.Robot) error); ok {
|
||||
r1 = rf(robot)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// DeleteByProjectID provides a mock function with given fields: ctx, projectID
|
||||
func (_m *RobotAccountDao) DeleteByProjectID(ctx context.Context, projectID int64) error {
|
||||
ret := _m.Called(ctx, projectID)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int64) error); ok {
|
||||
r0 = rf(ctx, projectID)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteRobotAccount provides a mock function with given fields: id
|
||||
func (_m *RobotAccountDao) DeleteRobotAccount(id int64) error {
|
||||
ret := _m.Called(id)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(int64) error); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetRobotAccount provides a mock function with given fields: id
|
||||
func (_m *RobotAccountDao) GetRobotAccount(id int64) (*model.Robot, error) {
|
||||
ret := _m.Called(id)
|
||||
|
||||
var r0 *model.Robot
|
||||
if rf, ok := ret.Get(0).(func(int64) *model.Robot); ok {
|
||||
r0 = rf(id)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*model.Robot)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(int64) error); ok {
|
||||
r1 = rf(id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// ListRobotAccounts provides a mock function with given fields: query
|
||||
func (_m *RobotAccountDao) ListRobotAccounts(query *q.Query) ([]*model.Robot, error) {
|
||||
ret := _m.Called(query)
|
||||
|
||||
var r0 []*model.Robot
|
||||
if rf, ok := ret.Get(0).(func(*q.Query) []*model.Robot); ok {
|
||||
r0 = rf(query)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.Robot)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(*q.Query) error); ok {
|
||||
r1 = rf(query)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// UpdateRobotAccount provides a mock function with given fields: robot
|
||||
func (_m *RobotAccountDao) UpdateRobotAccount(robot *model.Robot) error {
|
||||
ret := _m.Called(robot)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.Robot) error); ok {
|
||||
r0 = rf(robot)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
|
@ -28,7 +28,7 @@ def get_endpoint():
|
|||
|
||||
def _create_client(server, credential, debug, api_type="products"):
|
||||
cfg = None
|
||||
if api_type in ('projectv2', 'artifact', 'repository', 'scan', 'preheat', 'replication'):
|
||||
if api_type in ('projectv2', 'artifact', 'repository', 'scan', 'preheat', 'replication', 'robot'):
|
||||
cfg = v2_swagger_client.Configuration()
|
||||
else:
|
||||
cfg = swagger_client.Configuration()
|
||||
|
@ -60,6 +60,7 @@ def _create_client(server, credential, debug, api_type="products"):
|
|||
"scan": v2_swagger_client.ScanApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"scanner": swagger_client.ScannersApi(swagger_client.ApiClient(cfg)),
|
||||
"replication": v2_swagger_client.ReplicationApi(v2_swagger_client.ApiClient(cfg)),
|
||||
"robot": v2_swagger_client.RobotApi(v2_swagger_client.ApiClient(cfg)),
|
||||
}.get(api_type,'Error: Wrong API type')
|
||||
|
||||
def _assert_status_code(expect_code, return_code):
|
||||
|
|
|
@ -214,64 +214,6 @@ class Project(base.Base):
|
|||
base._assert_status_code(expect_status_code, status_code)
|
||||
return base._get_id_from_header(header)
|
||||
|
||||
def add_project_robot_account(self, project_id, project_name, expires_at, robot_name = None, robot_desc = None, has_pull_right = True, has_push_right = True, has_chart_read_right = True, has_chart_create_right = True, expect_status_code = 201, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
if robot_name is None:
|
||||
robot_name = base._random_name("robot")
|
||||
if robot_desc is None:
|
||||
robot_desc = base._random_name("robot_desc")
|
||||
if has_pull_right is False and has_push_right is False:
|
||||
has_pull_right = True
|
||||
access_list = []
|
||||
resource_by_project_id = "/project/"+str(project_id)+"/repository"
|
||||
resource_helm_by_project_id = "/project/"+str(project_id)+"/helm-chart"
|
||||
resource_helm_create_by_project_id = "/project/"+str(project_id)+"/helm-chart-version"
|
||||
action_pull = "pull"
|
||||
action_push = "push"
|
||||
action_read = "read"
|
||||
action_create = "create"
|
||||
if has_pull_right is True:
|
||||
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_id, action = action_pull)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_push_right is True:
|
||||
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_by_project_id, action = action_push)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_chart_read_right is True:
|
||||
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_helm_by_project_id, action = action_read)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_chart_create_right is True:
|
||||
robotAccountAccess = swagger_client.RobotAccountAccess(resource = resource_helm_create_by_project_id, action = action_create)
|
||||
access_list.append(robotAccountAccess)
|
||||
|
||||
robotAccountCreate = swagger_client.RobotAccountCreate(robot_name, robot_desc, expires_at, access_list)
|
||||
client = self._get_client(**kwargs)
|
||||
data = []
|
||||
data, status_code, header = client.projects_project_id_robots_post_with_http_info(project_id, robotAccountCreate)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(201, status_code)
|
||||
return base._get_id_from_header(header), data
|
||||
|
||||
def get_project_robot_account_by_id(self, project_id, robot_id, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
client = self._get_client(**kwargs)
|
||||
data, status_code, _ = client.projects_project_id_robots_robot_id_get_with_http_info(project_id, robot_id)
|
||||
return data
|
||||
|
||||
def disable_project_robot_account(self, project_id, robot_id, disable, expect_status_code = 200, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
client = self._get_client(**kwargs)
|
||||
robotAccountUpdate = swagger_client.RobotAccountUpdate(disable)
|
||||
_, status_code, _ = client.projects_project_id_robots_robot_id_put_with_http_info(project_id, robot_id, robotAccountUpdate)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(200, status_code)
|
||||
|
||||
def delete_project_robot_account(self, project_id, robot_id, expect_status_code = 200, **kwargs):
|
||||
kwargs['api_type'] = 'products'
|
||||
client = self._get_client(**kwargs)
|
||||
_, status_code, _ = client.projects_project_id_robots_robot_id_delete_with_http_info(project_id, robot_id)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(200, status_code)
|
||||
|
||||
def query_user_logs(self, project_name, status_code=200, **kwargs):
|
||||
try:
|
||||
logs = self.get_project_log(project_name, expect_status_code=status_code, **kwargs)
|
||||
|
|
67
tests/apitests/python/library/robot.py
Normal file
67
tests/apitests/python/library/robot.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import base
|
||||
import v2_swagger_client
|
||||
from v2_swagger_client.rest import ApiException
|
||||
|
||||
class Robot(base.Base, object):
|
||||
def __init__(self):
|
||||
super(Robot,self).__init__(api_type = "robot")
|
||||
|
||||
def create_project_robot(self, project_name, expires_at, robot_name = None, robot_desc = None, has_pull_right = True, has_push_right = True, has_chart_read_right = True, has_chart_create_right = True, expect_status_code = 201, **kwargs):
|
||||
if robot_name is None:
|
||||
robot_name = base._random_name("robot")
|
||||
if robot_desc is None:
|
||||
robot_desc = base._random_name("robot_desc")
|
||||
if has_pull_right is False and has_push_right is False:
|
||||
has_pull_right = True
|
||||
access_list = []
|
||||
action_pull = "pull"
|
||||
action_push = "push"
|
||||
action_read = "read"
|
||||
action_create = "create"
|
||||
if has_pull_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "repository", action = action_pull)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_push_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "repository", action = action_push)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_chart_read_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "helm-chart", action = action_read)
|
||||
access_list.append(robotAccountAccess)
|
||||
if has_chart_create_right is True:
|
||||
robotAccountAccess = v2_swagger_client.Access(resource = "helm-chart-version", action = action_create)
|
||||
access_list.append(robotAccountAccess)
|
||||
|
||||
robotaccountPermissions = v2_swagger_client.Permission(kind = "project", namespace = project_name, access = access_list)
|
||||
permission_list = []
|
||||
permission_list.append(robotaccountPermissions)
|
||||
robotAccountCreate = v2_swagger_client.RobotCreate(name=robot_name, description=robot_desc, expires_at=expires_at, level="project", permissions = permission_list)
|
||||
|
||||
client = self._get_client(**kwargs)
|
||||
data = []
|
||||
data, status_code, header = client.create_robot_with_http_info(robotAccountCreate)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(201, status_code)
|
||||
return base._get_id_from_header(header), data
|
||||
|
||||
def get_robot_account_by_id(self, robot_id, **kwargs):
|
||||
client = self._get_client(**kwargs)
|
||||
data, status_code, _ = client.get_robot_by_id_with_http_info(robot_id)
|
||||
return data
|
||||
|
||||
def disable_robot_account(self, robot_id, disable, expect_status_code = 200, **kwargs):
|
||||
client = self._get_client(**kwargs)
|
||||
data = self.get_robot_account_by_id(robot_id, **kwargs)
|
||||
robotAccountUpdate = v2_swagger_client.RobotCreate(name=data.name, description=data.description, expires_at=data.expires_at, level=data.level, permissions = data.permissions, disable = disable)
|
||||
|
||||
_, status_code, _ = client.update_robot_with_http_info(robot_id, robotAccountUpdate)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(200, status_code)
|
||||
|
||||
def delete_robot_account(self, robot_id, expect_status_code = 200, **kwargs):
|
||||
client = self._get_client(**kwargs)
|
||||
_, status_code, _ = client.delete_robot_with_http_info(robot_id)
|
||||
base._assert_status_code(expect_status_code, status_code)
|
||||
base._assert_status_code(200, status_code)
|
|
@ -8,6 +8,7 @@ from testutils import harbor_server
|
|||
from testutils import TEARDOWN
|
||||
import library.repository
|
||||
import library.helm
|
||||
from library.robot import Robot
|
||||
from library.project import Project
|
||||
from library.user import User
|
||||
from library.chart import Chart
|
||||
|
@ -18,6 +19,7 @@ class TestProjects(unittest.TestCase):
|
|||
self.project= Project()
|
||||
self.user= User()
|
||||
self.chart= Chart()
|
||||
self.robot = Robot()
|
||||
self.url = ADMIN_CLIENT["endpoint"]
|
||||
self.chart_api_url = CHART_API_CLIENT['endpoint']
|
||||
self.user_push_chart_password = "Aa123456"
|
||||
|
@ -56,17 +58,17 @@ class TestProjects(unittest.TestCase):
|
|||
|
||||
|
||||
#3. Create a new robot account(RA) with full priviliges in project(PA) with user(UA);
|
||||
robot_id, robot_account = self.project.add_project_robot_account(TestProjects.project_id, TestProjects.project_name,
|
||||
robot_id, robot_account = self.robot.create_project_robot(TestProjects.project_name,
|
||||
2441000531 ,**TestProjects.USER_CLIENT)
|
||||
#4. Push chart to project(PA) by Helm2 CLI with robot account(RA);"
|
||||
library.helm.helm2_add_repo(self.chart_repo_name, "https://"+harbor_server, TestProjects.project_name, robot_account.name, robot_account.token)
|
||||
library.helm.helm2_push(self.chart_repo_name, self.chart_file, TestProjects.project_name, robot_account.name, robot_account.token)
|
||||
library.helm.helm2_add_repo(self.chart_repo_name, "https://"+harbor_server, TestProjects.project_name, robot_account.name, robot_account.secret)
|
||||
library.helm.helm2_push(self.chart_repo_name, self.chart_file, TestProjects.project_name, robot_account.name, robot_account.secret)
|
||||
|
||||
#5. Get chart repositry from project(PA) successfully;
|
||||
self.chart.chart_should_exist(TestProjects.project_name, self.CHART_NAME, **TestProjects.API_CHART_CLIENT)
|
||||
|
||||
#6. Push chart to project(PA) by Helm3 CLI with robot account(RA);
|
||||
chart_cli_ret = library.helm.helm_chart_push_to_harbor(self.chart_file, self.archive, harbor_server, TestProjects.project_name, self.repo_name, self.verion, robot_account.name, robot_account.token)
|
||||
chart_cli_ret = library.helm.helm_chart_push_to_harbor(self.chart_file, self.archive, harbor_server, TestProjects.project_name, self.repo_name, self.verion, robot_account.name, robot_account.secret)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -7,6 +7,7 @@ from testutils import TEARDOWN
|
|||
from testutils import harbor_server
|
||||
from library.user import User
|
||||
from library.project import Project
|
||||
from library.robot import Robot
|
||||
from library.repository import Repository
|
||||
from library.repository import pull_harbor_image
|
||||
from library.repository import push_image_to_project
|
||||
|
@ -18,6 +19,7 @@ class TestProjects(unittest.TestCase):
|
|||
self.project = Project()
|
||||
self.user = User()
|
||||
self.repo = Repository()
|
||||
self.robot = Robot()
|
||||
|
||||
@unittest.skipIf(TEARDOWN == False, "Test data won't be erased.")
|
||||
def tearDown(self):
|
||||
|
@ -88,43 +90,43 @@ class TestProjects(unittest.TestCase):
|
|||
TestProjects.repo_name_in_project_b, tag_b = push_image_to_project(TestProjects.project_ra_name_b, harbor_server, user_ra_name, user_ra_password, image_project_b, tag)
|
||||
TestProjects.repo_name_in_project_c, tag_c = push_image_to_project(TestProjects.project_ra_name_c, harbor_server, user_ra_name, user_ra_password, image_project_c, tag)
|
||||
|
||||
#4. Create a new robot account(RA) with pull and push privilige in project(PA) by user(UA);
|
||||
robot_id, robot_account = self.project.add_project_robot_account(TestProjects.project_ra_id_a, TestProjects.project_ra_name_a,
|
||||
#4. Create a new robot account(RA) with pull and push privilege in project(PA) by user(UA);
|
||||
robot_id, robot_account = self.robot.create_project_robot(TestProjects.project_ra_name_a,
|
||||
2441000531 ,**TestProjects.USER_RA_CLIENT)
|
||||
|
||||
#5. Check robot account info, it should has both pull and push priviliges;
|
||||
data = self.project.get_project_robot_account_by_id(TestProjects.project_ra_id_a, robot_id, **TestProjects.USER_RA_CLIENT)
|
||||
#5. Check robot account info, it should has both pull and push privilege;
|
||||
data = self.robot.get_robot_account_by_id(robot_id, **TestProjects.USER_RA_CLIENT)
|
||||
_assert_status_code(robot_account.name, data.name)
|
||||
|
||||
#6. Pull image(ImagePA) from project(PA) by robot account(RA), it must be successful;
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_a, tag_a)
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.secret, TestProjects.repo_name_in_project_a, tag_a)
|
||||
|
||||
#7. Push image(ImageRA) to project(PA) by robot account(RA), it must be successful;
|
||||
TestProjects.repo_name_pa, _ = push_image_to_project(TestProjects.project_ra_name_a, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag)
|
||||
TestProjects.repo_name_pa, _ = push_image_to_project(TestProjects.project_ra_name_a, harbor_server, robot_account.name, robot_account.secret, image_robot_account, tag)
|
||||
|
||||
#8. Push image(ImageRA) to project(PB) by robot account(RA), it must be not successful;
|
||||
push_image_to_project(TestProjects.project_ra_name_b, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_error_message = "unauthorized to access repository")
|
||||
push_image_to_project(TestProjects.project_ra_name_b, harbor_server, robot_account.name, robot_account.secret, image_robot_account, tag, expected_error_message = "unauthorized to access repository")
|
||||
|
||||
#9. Pull image(ImagePB) from project(PB) by robot account(RA), it must be not successful;
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_b, tag_b, expected_error_message = "unauthorized to access repository")
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.secret, TestProjects.repo_name_in_project_b, tag_b, expected_error_message = "unauthorized to access repository")
|
||||
|
||||
#10. Pull image from project(PC), it must be successful;
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_c, tag_c)
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.secret, TestProjects.repo_name_in_project_c, tag_c)
|
||||
|
||||
#11. Push image(ImageRA) to project(PC) by robot account(RA), it must be not successful;
|
||||
push_image_to_project(TestProjects.project_ra_name_c, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_error_message = "unauthorized to access repository")
|
||||
push_image_to_project(TestProjects.project_ra_name_c, harbor_server, robot_account.name, robot_account.secret, image_robot_account, tag, expected_error_message = "unauthorized to access repository")
|
||||
|
||||
#12. Update action property of robot account(RA);"
|
||||
self.project.disable_project_robot_account(TestProjects.project_ra_id_a, robot_id, True, **TestProjects.USER_RA_CLIENT)
|
||||
self.robot.disable_robot_account(robot_id, True, **TestProjects.USER_RA_CLIENT)
|
||||
|
||||
#13. Pull image(ImagePA) from project(PA) by robot account(RA), it must be not successful;
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.token, TestProjects.repo_name_in_project_a, tag_a, expected_login_error_message = "unauthorized: authentication required")
|
||||
pull_harbor_image(harbor_server, robot_account.name, robot_account.secret, TestProjects.repo_name_in_project_a, tag_a, expected_login_error_message = "unauthorized: authentication required")
|
||||
|
||||
#14. Push image(ImageRA) to project(PA) by robot account(RA), it must be not successful;
|
||||
push_image_to_project(TestProjects.project_ra_name_a, harbor_server, robot_account.name, robot_account.token, image_robot_account, tag, expected_login_error_message = "unauthorized: authentication required")
|
||||
push_image_to_project(TestProjects.project_ra_name_a, harbor_server, robot_account.name, robot_account.secret, image_robot_account, tag, expected_login_error_message = "unauthorized: authentication required")
|
||||
|
||||
#15. Delete robot account(RA), it must be not successful.
|
||||
self.project.delete_project_robot_account(TestProjects.project_ra_id_a, robot_id, **TestProjects.USER_RA_CLIENT)
|
||||
self.robot.delete_robot_account(robot_id, **TestProjects.USER_RA_CLIENT)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
Loading…
Reference in New Issue
Block a user