mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-29 13:57:33 +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'
|
$ref: '#/definitions/NotFoundChartAPIError'
|
||||||
'500':
|
'500':
|
||||||
$ref: '#/definitions/InternalChartAPIError'
|
$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':
|
'/system/oidc/ping':
|
||||||
post:
|
post:
|
||||||
summary: Test the OIDC endpoint.
|
summary: Test the OIDC endpoint.
|
||||||
@ -4633,75 +4452,6 @@ definitions:
|
|||||||
error:
|
error:
|
||||||
type: string
|
type: string
|
||||||
description: (optional) The error message when the status is "unhealthy"
|
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:
|
Permission:
|
||||||
type: object
|
type: object
|
||||||
description: The permission
|
description: The permission
|
||||||
|
@ -23,12 +23,13 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/controller/project"
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
"github.com/goharbor/harbor/src/pkg/permission/evaluator"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SecurityContext implements security.Context interface based on database
|
// SecurityContext implements security.Context interface based on database
|
||||||
type SecurityContext struct {
|
type SecurityContext struct {
|
||||||
robot *model.Robot
|
robot *model.Robot
|
||||||
|
isSystemLevel bool
|
||||||
ctl project.Controller
|
ctl project.Controller
|
||||||
policy []*types.Policy
|
policy []*types.Policy
|
||||||
evaluator evaluator.Evaluator
|
evaluator evaluator.Evaluator
|
||||||
@ -36,7 +37,7 @@ type SecurityContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewSecurityContext ...
|
// NewSecurityContext ...
|
||||||
func NewSecurityContext(robot *model.Robot, policy []*types.Policy) *SecurityContext {
|
func NewSecurityContext(robot *model.Robot, isSystemLevel bool, policy []*types.Policy) *SecurityContext {
|
||||||
return &SecurityContext{
|
return &SecurityContext{
|
||||||
ctl: project.Ctl,
|
ctl: project.Ctl,
|
||||||
robot: robot,
|
robot: robot,
|
||||||
@ -76,7 +77,11 @@ func (s *SecurityContext) IsSolutionUser() bool {
|
|||||||
// Can returns whether the robot can do action on resource
|
// Can returns whether the robot can do action on resource
|
||||||
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
func (s *SecurityContext) Can(ctx context.Context, action types.Action, resource types.Resource) bool {
|
||||||
s.once.Do(func() {
|
s.once.Do(func() {
|
||||||
|
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))
|
s.evaluator = rbac.NewProjectEvaluator(s.ctl, rbac.NewBuilderForPolicies(s.GetUsername(), s.policy, filterRobotPolicies))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return s.evaluator != nil && s.evaluator.HasPermission(ctx, resource, action)
|
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/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||||
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
projecttesting "github.com/goharbor/harbor/src/testing/controller/project"
|
||||||
"github.com/goharbor/harbor/src/testing/mock"
|
"github.com/goharbor/harbor/src/testing/mock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -38,45 +38,45 @@ var (
|
|||||||
|
|
||||||
func TestIsAuthenticated(t *testing.T) {
|
func TestIsAuthenticated(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil, false, nil)
|
||||||
assert.False(t, ctx.IsAuthenticated())
|
assert.False(t, ctx.IsAuthenticated())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&model.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil)
|
}, false, nil)
|
||||||
assert.True(t, ctx.IsAuthenticated())
|
assert.True(t, ctx.IsAuthenticated())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUsername(t *testing.T) {
|
func TestGetUsername(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil, false, nil)
|
||||||
assert.Equal(t, "", ctx.GetUsername())
|
assert.Equal(t, "", ctx.GetUsername())
|
||||||
|
|
||||||
// authenticated
|
// authenticated
|
||||||
ctx = NewSecurityContext(&model.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil)
|
}, false, nil)
|
||||||
assert.Equal(t, "test", ctx.GetUsername())
|
assert.Equal(t, "test", ctx.GetUsername())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsSysAdmin(t *testing.T) {
|
func TestIsSysAdmin(t *testing.T) {
|
||||||
// unauthenticated
|
// unauthenticated
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil, false, nil)
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
assert.False(t, ctx.IsSysAdmin())
|
||||||
|
|
||||||
// authenticated, non admin
|
// authenticated, non admin
|
||||||
ctx = NewSecurityContext(&model.Robot{
|
ctx = NewSecurityContext(&model.Robot{
|
||||||
Name: "test",
|
Name: "test",
|
||||||
Disabled: false,
|
Disabled: false,
|
||||||
}, nil)
|
}, false, nil)
|
||||||
assert.False(t, ctx.IsSysAdmin())
|
assert.False(t, ctx.IsSysAdmin())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsSolutionUser(t *testing.T) {
|
func TestIsSolutionUser(t *testing.T) {
|
||||||
ctx := NewSecurityContext(nil, nil)
|
ctx := NewSecurityContext(nil, false, nil)
|
||||||
assert.False(t, ctx.IsSolutionUser())
|
assert.False(t, ctx.IsSolutionUser())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ func TestHasPullPerm(t *testing.T) {
|
|||||||
ctl := &projecttesting.Controller{}
|
ctl := &projecttesting.Controller{}
|
||||||
mock.OnAnything(ctl, "Get").Return(private, nil)
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
ctx := NewSecurityContext(robot, policies)
|
ctx := NewSecurityContext(robot, false, policies)
|
||||||
ctx.ctl = ctl
|
ctx.ctl = ctl
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPull, resource))
|
||||||
@ -116,7 +116,7 @@ func TestHasPushPerm(t *testing.T) {
|
|||||||
ctl := &projecttesting.Controller{}
|
ctl := &projecttesting.Controller{}
|
||||||
mock.OnAnything(ctl, "Get").Return(private, nil)
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
ctx := NewSecurityContext(robot, policies)
|
ctx := NewSecurityContext(robot, false, policies)
|
||||||
ctx.ctl = ctl
|
ctx.ctl = ctl
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
||||||
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
assert.True(t, ctx.Can(context.TODO(), rbac.ActionPush, resource))
|
||||||
@ -141,7 +141,7 @@ func TestHasPushPullPerm(t *testing.T) {
|
|||||||
ctl := &projecttesting.Controller{}
|
ctl := &projecttesting.Controller{}
|
||||||
mock.OnAnything(ctl, "Get").Return(private, nil)
|
mock.OnAnything(ctl, "Get").Return(private, nil)
|
||||||
|
|
||||||
ctx := NewSecurityContext(robot, policies)
|
ctx := NewSecurityContext(robot, false, policies)
|
||||||
ctx.ctl = ctl
|
ctx.ctl = ctl
|
||||||
resource := rbac.NewProjectNamespace(private.ProjectID).Resource(rbac.ResourceRepository)
|
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))
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
r.ID = robotID
|
||||||
if err := d.createPermission(ctx, r); err != nil {
|
if err := d.createPermission(ctx, r); err != nil {
|
||||||
return 0, err
|
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)
|
log.Errorf("failed to decode scope of robot %d: %v", r.ID, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
p.Scope = scope
|
||||||
p.Kind = kind
|
p.Kind = kind
|
||||||
p.Namespace = namespace
|
p.Namespace = namespace
|
||||||
p.Access = accesses
|
p.Access = accesses
|
||||||
|
@ -42,6 +42,7 @@ type Permission struct {
|
|||||||
Kind string `json:"kind"`
|
Kind string `json:"kind"`
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
Access []*types.Policy `json:"access"`
|
Access []*types.Policy `json:"access"`
|
||||||
|
Scope string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option ...
|
// Option ...
|
||||||
|
@ -18,20 +18,21 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/goharbor/harbor/src/controller/project"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
cj "github.com/goharbor/harbor/src/common/job"
|
cj "github.com/goharbor/harbor/src/common/job"
|
||||||
jm "github.com/goharbor/harbor/src/common/job/models"
|
jm "github.com/goharbor/harbor/src/common/job/models"
|
||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
ar "github.com/goharbor/harbor/src/controller/artifact"
|
ar "github.com/goharbor/harbor/src/controller/artifact"
|
||||||
|
"github.com/goharbor/harbor/src/controller/robot"
|
||||||
sc "github.com/goharbor/harbor/src/controller/scanner"
|
sc "github.com/goharbor/harbor/src/controller/scanner"
|
||||||
"github.com/goharbor/harbor/src/core/config"
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
"github.com/goharbor/harbor/src/jobservice/job"
|
"github.com/goharbor/harbor/src/jobservice/job"
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
"github.com/goharbor/harbor/src/pkg/permission/types"
|
"github.com/goharbor/harbor/src/pkg/permission/types"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot"
|
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
|
||||||
sca "github.com/goharbor/harbor/src/pkg/scan"
|
sca "github.com/goharbor/harbor/src/pkg/scan"
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/all"
|
"github.com/goharbor/harbor/src/pkg/scan/all"
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
"github.com/goharbor/harbor/src/pkg/scan/dao/scan"
|
||||||
@ -70,6 +71,8 @@ type basicController struct {
|
|||||||
sc sc.Controller
|
sc sc.Controller
|
||||||
// Robot account controller
|
// Robot account controller
|
||||||
rc robot.Controller
|
rc robot.Controller
|
||||||
|
// Project controller
|
||||||
|
pro project.Controller
|
||||||
// Job service client
|
// Job service client
|
||||||
jc jcGetter
|
jc jcGetter
|
||||||
// UUID generator
|
// UUID generator
|
||||||
@ -88,7 +91,9 @@ func NewController() Controller {
|
|||||||
// Refer to the default scanner controller
|
// Refer to the default scanner controller
|
||||||
sc: sc.DefaultController,
|
sc: sc.DefaultController,
|
||||||
// Refer to the default robot account controller
|
// 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
|
// Refer to the default job service client
|
||||||
jc: func() cj.Client {
|
jc: func() cj.Client {
|
||||||
return cj.GlobalClient
|
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 {
|
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 {
|
if err != nil {
|
||||||
// Update the status to the concrete error
|
// Update the status to the concrete error
|
||||||
// Change status code to normal error code
|
// Change status code to normal error code
|
||||||
@ -494,7 +499,7 @@ func (bc *basicController) GetScanLog(uuid string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HandleJobHooks ...
|
// 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 {
|
if len(trackID) == 0 {
|
||||||
return errors.New("empty track ID")
|
return errors.New("empty track ID")
|
||||||
}
|
}
|
||||||
@ -514,7 +519,7 @@ func (bc *basicController) HandleJobHooks(trackID string, change *job.StatusChan
|
|||||||
}
|
}
|
||||||
|
|
||||||
if r.ID > 0 {
|
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
|
// Should not block the main flow, just logged
|
||||||
log.Error(errors.Wrap(err, "scan controller: handle job hook"))
|
log.Error(errors.Wrap(err, "scan controller: handle job hook"))
|
||||||
} else {
|
} 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.
|
// 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.
|
// Use uuid as name to avoid duplicated entries.
|
||||||
UUID, err := bc.uuid()
|
UUID, err := bc.uuid()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "scan controller: make robot account")
|
return nil, errors.Wrap(err, "scan controller: make robot account")
|
||||||
}
|
}
|
||||||
|
|
||||||
resource := rbac.NewProjectNamespace(projectID).Resource(rbac.ResourceRepository)
|
p, err := bc.pro.Get(ctx, projectID)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "scan controller: make robot account")
|
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
|
// 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
|
var ck string
|
||||||
if registration.UseInternalAddr {
|
if registration.UseInternalAddr {
|
||||||
ck = configCoreInternalAddr
|
ck = configCoreInternalAddr
|
||||||
@ -618,7 +648,7 @@ func (bc *basicController) launchScanJob(trackID string, artifact *ar.Artifact,
|
|||||||
return "", errors.Wrap(err, "scan controller: launch scan job")
|
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 {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "scan controller: launch scan job")
|
return "", errors.Wrap(err, "scan controller: launch scan job")
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,8 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
models "github.com/goharbor/harbor/src/common/models"
|
||||||
|
"github.com/goharbor/harbor/src/controller/robot"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -27,15 +29,16 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common/rbac"
|
"github.com/goharbor/harbor/src/common/rbac"
|
||||||
"github.com/goharbor/harbor/src/controller/artifact"
|
"github.com/goharbor/harbor/src/controller/artifact"
|
||||||
"github.com/goharbor/harbor/src/jobservice/job"
|
"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/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"
|
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/scan"
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||||
artifacttesting "github.com/goharbor/harbor/src/testing/controller/artifact"
|
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"
|
scannertesting "github.com/goharbor/harbor/src/testing/controller/scanner"
|
||||||
mocktesting "github.com/goharbor/harbor/src/testing/mock"
|
mocktesting "github.com/goharbor/harbor/src/testing/mock"
|
||||||
reporttesting "github.com/goharbor/harbor/src/testing/pkg/scan/report"
|
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)
|
mgr.On("UpdateStatus", "the-uuid-123", "Success", (int64)(10000)).Return(nil)
|
||||||
suite.reportMgr = mgr
|
suite.reportMgr = mgr
|
||||||
|
|
||||||
rc := &MockRobotController{}
|
rc := &robottesting.Controller{}
|
||||||
|
|
||||||
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},
|
|
||||||
}
|
|
||||||
|
|
||||||
rname := fmt.Sprintf("%s-%s", suite.registration.Name, "the-uuid-123")
|
rname := fmt.Sprintf("%s-%s", suite.registration.Name, "the-uuid-123")
|
||||||
account := &model.RobotCreate{
|
|
||||||
|
account := &robot.Robot{
|
||||||
|
Robot: model.Robot{
|
||||||
Name: rname,
|
Name: rname,
|
||||||
Description: "for scan",
|
Description: "for scan",
|
||||||
ProjectID: suite.artifact.ProjectID,
|
ProjectID: suite.artifact.ProjectID,
|
||||||
Access: access,
|
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{
|
|
||||||
|
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,
|
ID: 1,
|
||||||
Name: rname,
|
Name: rname,
|
||||||
Token: "robot-account",
|
Secret: "robot-account",
|
||||||
Description: "for scan",
|
Description: "for scan",
|
||||||
ProjectID: suite.artifact.ProjectID,
|
ProjectID: suite.artifact.ProjectID,
|
||||||
|
},
|
||||||
|
Level: "project",
|
||||||
}, nil)
|
}, nil)
|
||||||
|
|
||||||
// Set job parameters
|
// Set job parameters
|
||||||
@ -208,7 +232,8 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
|||||||
regJSON, err := suite.registration.ToJSON()
|
regJSON, err := suite.registration.ToJSON()
|
||||||
require.NoError(suite.T(), err)
|
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()
|
robotJSON, err := rb.ToJSON()
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
@ -237,6 +262,12 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
|||||||
walkFn(suite.artifact)
|
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{
|
suite.c = &basicController{
|
||||||
manager: mgr,
|
manager: mgr,
|
||||||
ar: suite.ar,
|
ar: suite.ar,
|
||||||
@ -245,6 +276,7 @@ func (suite *ControllerTestSuite) SetupSuite() {
|
|||||||
return jc
|
return jc
|
||||||
},
|
},
|
||||||
rc: rc,
|
rc: rc,
|
||||||
|
pro: proCtl,
|
||||||
uuid: func() (string, error) {
|
uuid: func() (string, error) {
|
||||||
return "the-uuid-123", nil
|
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)
|
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)
|
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:
|
// Returns:
|
||||||
// error : non nil error if any errors occurred
|
// 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
|
// 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/CVEAllowlist", &SysCVEAllowlistAPI{}, "get:Get;put:Put")
|
||||||
beego.Router("/api/system/oidc/ping", &OIDCAPI{}, "post:Ping")
|
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/adapters", &ReplicationAdapterAPI{}, "get:List")
|
||||||
|
|
||||||
beego.Router("/api/replication/policies", &ReplicationPolicyAPI{}, "get:List;post:Create")
|
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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/goharbor/harbor/src/lib/orm"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/common/job"
|
"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")
|
err = errors.Wrap(err, "scan job hook handler")
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
h.SendInternalServerError(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
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/goharbor/harbor/src/lib/errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/astaxie/beego/orm"
|
"github.com/astaxie/beego/orm"
|
||||||
@ -28,3 +30,22 @@ type Robot struct {
|
|||||||
func (r *Robot) TableName() string {
|
func (r *Robot) TableName() string {
|
||||||
return "robot"
|
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/job"
|
||||||
"github.com/goharbor/harbor/src/jobservice/logger"
|
"github.com/goharbor/harbor/src/jobservice/logger"
|
||||||
"github.com/goharbor/harbor/src/lib/errors"
|
"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/dao/scanner"
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/report"
|
"github.com/goharbor/harbor/src/pkg/scan/report"
|
||||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
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.
|
// makeBasicAuthorization creates authorization from a robot account based on the arguments for scanning.
|
||||||
func makeBasicAuthorization(robotAccount *model.Robot) (string, error) {
|
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))
|
encoded := base64.StdEncoding.EncodeToString([]byte(basic))
|
||||||
|
|
||||||
return fmt.Sprintf("Basic %s", encoded), nil
|
return fmt.Sprintf("Basic %s", encoded), nil
|
||||||
|
@ -16,11 +16,12 @@ package scan
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/goharbor/harbor/src/controller/robot"
|
||||||
|
"github.com/goharbor/harbor/src/pkg/robot2/model"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/goharbor/harbor/src/jobservice/job"
|
"github.com/goharbor/harbor/src/jobservice/job"
|
||||||
"github.com/goharbor/harbor/src/pkg/robot/model"
|
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
"github.com/goharbor/harbor/src/pkg/scan/dao/scanner"
|
||||||
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
v1 "github.com/goharbor/harbor/src/pkg/scan/rest/v1"
|
||||||
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
"github.com/goharbor/harbor/src/pkg/scan/vuln"
|
||||||
@ -90,10 +91,13 @@ func (suite *JobTestSuite) TestJob() {
|
|||||||
sData, err := sr.ToJSON()
|
sData, err := sr.ToJSON()
|
||||||
require.NoError(suite.T(), err)
|
require.NoError(suite.T(), err)
|
||||||
|
|
||||||
robot := &model.Robot{
|
robot := &robot.Robot{
|
||||||
|
Robot: model.Robot{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Name: "robot",
|
Name: "robot",
|
||||||
Token: "token",
|
Secret: "token",
|
||||||
|
},
|
||||||
|
Level: "project",
|
||||||
}
|
}
|
||||||
|
|
||||||
robotData, err := robot.ToJSON()
|
robotData, err := robot.ToJSON()
|
||||||
|
@ -21,8 +21,8 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/common"
|
"github.com/goharbor/harbor/src/common"
|
||||||
"github.com/goharbor/harbor/src/common/security"
|
"github.com/goharbor/harbor/src/common/security"
|
||||||
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
robotCtx "github.com/goharbor/harbor/src/common/security/robot"
|
||||||
|
ctl_robot "github.com/goharbor/harbor/src/controller/robot"
|
||||||
"github.com/goharbor/harbor/src/lib/log"
|
"github.com/goharbor/harbor/src/lib/log"
|
||||||
pkgrobot "github.com/goharbor/harbor/src/pkg/robot"
|
|
||||||
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
pkg_token "github.com/goharbor/harbor/src/pkg/token"
|
||||||
robot_claim "github.com/goharbor/harbor/src/pkg/token/claims/robot"
|
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
|
return nil
|
||||||
}
|
}
|
||||||
// Do authn for robot account, as Harbor only stores the token ID, just validate the ID and disable.
|
// Do authn for robot account, as Harbor only stores the token ID, just validate the ID and disable.
|
||||||
ctr := pkgrobot.RobotCtr
|
ctr := ctl_robot.Ctl
|
||||||
robot, err := ctr.GetRobotAccount(rtk.Claims.(*robot_claim.Claim).TokenID)
|
robot, err := ctr.Get(req.Context(), rtk.Claims.(*robot_claim.Claim).TokenID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failed to get robot %s: %v", robotName, err)
|
log.Errorf("failed to get robot %s: %v", robotName, err)
|
||||||
return nil
|
return nil
|
||||||
@ -65,5 +65,6 @@ func (r *robot) Generate(req *http.Request) security.Context {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Debugf("a robot security context generated for request %s %s", req.Method, req.URL.Path)
|
log.Debugf("a robot security context generated for request %s %s", req.Method, req.URL.Path)
|
||||||
return robotCtx.NewSecurityContext(robot, 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{},
|
&idToken{},
|
||||||
&authProxy{},
|
&authProxy{},
|
||||||
&robot{},
|
&robot{},
|
||||||
|
&robot2{},
|
||||||
&basicAuth{},
|
&basicAuth{},
|
||||||
&session{},
|
&session{},
|
||||||
&proxyCacheSecret{},
|
&proxyCacheSecret{},
|
||||||
|
@ -7,10 +7,12 @@ import (
|
|||||||
"github.com/goharbor/harbor/src/server/v2.0/models"
|
"github.com/goharbor/harbor/src/server/v2.0/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Robot ...
|
||||||
type Robot struct {
|
type Robot struct {
|
||||||
*robot.Robot
|
*robot.Robot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToSwagger ...
|
||||||
func (r *Robot) ToSwagger() *models.Robot {
|
func (r *Robot) ToSwagger() *models.Robot {
|
||||||
perms := []*models.Permission{}
|
perms := []*models.Permission{}
|
||||||
for _, p := range r.Permissions {
|
for _, p := range r.Permissions {
|
||||||
|
@ -120,7 +120,6 @@ func (rAPI *robotAPI) ListRobot(ctx context.Context, params operation.ListRobotP
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
level = robot.LEVELSYSTEM
|
level = robot.LEVELSYSTEM
|
||||||
projectID = 0
|
|
||||||
query.Keywords["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)
|
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.
|
// to create a project robot, the permission must be only one project scope.
|
||||||
if len(r.Permissions) > 1 {
|
if r.Level == robot.LEVELPROJECT && len(r.Permissions) > 1 {
|
||||||
return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
|
return errors.New(nil).WithMessage("bad request permission").WithCode(errors.BadRequestCode)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValidLevel(l string) bool {
|
func isValidLevel(l string) bool {
|
||||||
switch l {
|
return l == robot.LEVELSYSTEM || l == robot.LEVELPROJECT
|
||||||
case
|
|
||||||
robot.LEVELSYSTEM,
|
|
||||||
robot.LEVELPROJECT:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
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+"/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/?:name", &api.MetadataAPI{}, "get:Get")
|
||||||
beego.Router("/api/"+version+"/projects/:id([0-9]+)/metadatas/", &api.MetadataAPI{}, "post:Post")
|
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", &api.QuotaAPI{}, "get:List")
|
||||||
beego.Router("/api/"+version+"/quotas/:id([0-9]+)", &api.QuotaAPI{}, "get:Get;put:Put")
|
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/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/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/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
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleJobHooks provides a mock function with given fields: trackID, change
|
// HandleJobHooks provides a mock function with given fields: ctx, trackID, change
|
||||||
func (_m *Controller) HandleJobHooks(trackID string, change *job.StatusChange) error {
|
func (_m *Controller) HandleJobHooks(ctx context.Context, trackID string, change *job.StatusChange) error {
|
||||||
ret := _m.Called(trackID, change)
|
ret := _m.Called(ctx, trackID, change)
|
||||||
|
|
||||||
var r0 error
|
var r0 error
|
||||||
if rf, ok := ret.Get(0).(func(string, *job.StatusChange) error); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, string, *job.StatusChange) error); ok {
|
||||||
r0 = rf(trackID, change)
|
r0 = rf(ctx, trackID, change)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Error(0)
|
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 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/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/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 --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/rbac/dao --name DAO --output ./rbac/dao --outpkg dao
|
||||||
//go:generate mockery --case snake --dir ../../pkg/robot2 --name Manager --output ./robot2 --outpkg robot2
|
//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"):
|
def _create_client(server, credential, debug, api_type="products"):
|
||||||
cfg = None
|
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()
|
cfg = v2_swagger_client.Configuration()
|
||||||
else:
|
else:
|
||||||
cfg = swagger_client.Configuration()
|
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)),
|
"scan": v2_swagger_client.ScanApi(v2_swagger_client.ApiClient(cfg)),
|
||||||
"scanner": swagger_client.ScannersApi(swagger_client.ApiClient(cfg)),
|
"scanner": swagger_client.ScannersApi(swagger_client.ApiClient(cfg)),
|
||||||
"replication": v2_swagger_client.ReplicationApi(v2_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')
|
}.get(api_type,'Error: Wrong API type')
|
||||||
|
|
||||||
def _assert_status_code(expect_code, return_code):
|
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)
|
base._assert_status_code(expect_status_code, status_code)
|
||||||
return base._get_id_from_header(header)
|
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):
|
def query_user_logs(self, project_name, status_code=200, **kwargs):
|
||||||
try:
|
try:
|
||||||
logs = self.get_project_log(project_name, expect_status_code=status_code, **kwargs)
|
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
|
from testutils import TEARDOWN
|
||||||
import library.repository
|
import library.repository
|
||||||
import library.helm
|
import library.helm
|
||||||
|
from library.robot import Robot
|
||||||
from library.project import Project
|
from library.project import Project
|
||||||
from library.user import User
|
from library.user import User
|
||||||
from library.chart import Chart
|
from library.chart import Chart
|
||||||
@ -18,6 +19,7 @@ class TestProjects(unittest.TestCase):
|
|||||||
self.project= Project()
|
self.project= Project()
|
||||||
self.user= User()
|
self.user= User()
|
||||||
self.chart= Chart()
|
self.chart= Chart()
|
||||||
|
self.robot = Robot()
|
||||||
self.url = ADMIN_CLIENT["endpoint"]
|
self.url = ADMIN_CLIENT["endpoint"]
|
||||||
self.chart_api_url = CHART_API_CLIENT['endpoint']
|
self.chart_api_url = CHART_API_CLIENT['endpoint']
|
||||||
self.user_push_chart_password = "Aa123456"
|
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);
|
#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)
|
2441000531 ,**TestProjects.USER_CLIENT)
|
||||||
#4. Push chart to project(PA) by Helm2 CLI with robot account(RA);"
|
#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_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.token)
|
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;
|
#5. Get chart repositry from project(PA) successfully;
|
||||||
self.chart.chart_should_exist(TestProjects.project_name, self.CHART_NAME, **TestProjects.API_CHART_CLIENT)
|
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);
|
#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__':
|
if __name__ == '__main__':
|
||||||
|
@ -7,6 +7,7 @@ from testutils import TEARDOWN
|
|||||||
from testutils import harbor_server
|
from testutils import harbor_server
|
||||||
from library.user import User
|
from library.user import User
|
||||||
from library.project import Project
|
from library.project import Project
|
||||||
|
from library.robot import Robot
|
||||||
from library.repository import Repository
|
from library.repository import Repository
|
||||||
from library.repository import pull_harbor_image
|
from library.repository import pull_harbor_image
|
||||||
from library.repository import push_image_to_project
|
from library.repository import push_image_to_project
|
||||||
@ -18,6 +19,7 @@ class TestProjects(unittest.TestCase):
|
|||||||
self.project = Project()
|
self.project = Project()
|
||||||
self.user = User()
|
self.user = User()
|
||||||
self.repo = Repository()
|
self.repo = Repository()
|
||||||
|
self.robot = Robot()
|
||||||
|
|
||||||
@unittest.skipIf(TEARDOWN == False, "Test data won't be erased.")
|
@unittest.skipIf(TEARDOWN == False, "Test data won't be erased.")
|
||||||
def tearDown(self):
|
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_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)
|
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);
|
#4. Create a new robot account(RA) with pull and push privilege 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,
|
robot_id, robot_account = self.robot.create_project_robot(TestProjects.project_ra_name_a,
|
||||||
2441000531 ,**TestProjects.USER_RA_CLIENT)
|
2441000531 ,**TestProjects.USER_RA_CLIENT)
|
||||||
|
|
||||||
#5. Check robot account info, it should has both pull and push priviliges;
|
#5. Check robot account info, it should has both pull and push privilege;
|
||||||
data = self.project.get_project_robot_account_by_id(TestProjects.project_ra_id_a, robot_id, **TestProjects.USER_RA_CLIENT)
|
data = self.robot.get_robot_account_by_id(robot_id, **TestProjects.USER_RA_CLIENT)
|
||||||
_assert_status_code(robot_account.name, data.name)
|
_assert_status_code(robot_account.name, data.name)
|
||||||
|
|
||||||
#6. Pull image(ImagePA) from project(PA) by robot account(RA), it must be successful;
|
#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;
|
#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;
|
#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;
|
#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;
|
#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;
|
#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);"
|
#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;
|
#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;
|
#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.
|
#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__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
Loading…
Reference in New Issue
Block a user