mirror of
https://github.com/goharbor/harbor.git
synced 2024-10-01 23:07:39 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
cadc1187c2
2
Makefile
2
Makefile
@ -92,7 +92,7 @@ REBUILDCLARITYFLAG=false
|
||||
NEWCLARITYVERSION=
|
||||
|
||||
#clair parameters
|
||||
CLAIRVERSION=v2.0.0
|
||||
CLAIRVERSION=v2.0.1
|
||||
CLAIRFLAG=false
|
||||
CLAIRDBVERSION=9.6.3-photon
|
||||
|
||||
|
@ -47,7 +47,7 @@ create table user (
|
||||
# 11 bytes is reserved for marking the deleted users.
|
||||
email varchar(255),
|
||||
password varchar(40) NOT NULL,
|
||||
realname varchar (20) NOT NULL,
|
||||
realname varchar (255) NOT NULL,
|
||||
comment varchar (30),
|
||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||
reset_uuid varchar(40) DEFAULT NULL,
|
||||
@ -194,6 +194,14 @@ create table img_scan_overview (
|
||||
PRIMARY KEY(image_digest)
|
||||
);
|
||||
|
||||
create table clair_vuln_timestamp (
|
||||
id int NOT NULL AUTO_INCREMENT,
|
||||
namespace varchar(128) NOT NULL,
|
||||
last_update timestamp NOT NULL,
|
||||
PRIMARY KEY(id),
|
||||
UNIQUE(namespace)
|
||||
);
|
||||
|
||||
create table properties (
|
||||
k varchar(64) NOT NULL,
|
||||
v varchar(128) NOT NULL,
|
||||
|
@ -44,7 +44,7 @@ create table user (
|
||||
*/
|
||||
email varchar(255),
|
||||
password varchar(40) NOT NULL,
|
||||
realname varchar (20) NOT NULL,
|
||||
realname varchar (255) NOT NULL,
|
||||
comment varchar (30),
|
||||
deleted tinyint (1) DEFAULT 0 NOT NULL,
|
||||
reset_uuid varchar(40) DEFAULT NULL,
|
||||
@ -150,7 +150,7 @@ create table replication_target (
|
||||
);
|
||||
|
||||
create table replication_job (
|
||||
id INTEGER PRIMARY KEY,
|
||||
id INTEGER PRIMARY KEY,
|
||||
status varchar(64) NOT NULL,
|
||||
policy_id int NOT NULL,
|
||||
repository varchar(256) NOT NULL,
|
||||
@ -187,6 +187,13 @@ create table img_scan_overview (
|
||||
CREATE INDEX policy ON replication_job (policy_id);
|
||||
CREATE INDEX poid_uptime ON replication_job (policy_id, update_time);
|
||||
|
||||
create table clair_vuln_timestamp (
|
||||
id INTEGER PRIMARY KEY,
|
||||
namespace varchar(128) NOT NULL,
|
||||
last_update timestamp NOT NULL,
|
||||
UNIQUE(namespace)
|
||||
);
|
||||
|
||||
create table properties (
|
||||
k varchar(64) NOT NULL,
|
||||
v varchar(128) NOT NULL,
|
||||
|
@ -16,8 +16,10 @@ clair:
|
||||
# Deadline before an API request will respond with a 503
|
||||
timeout: 300s
|
||||
updater:
|
||||
interval: 2h
|
||||
interval: 1h
|
||||
|
||||
notifier:
|
||||
attempts: 3
|
||||
renotifyinterval: 2h
|
||||
http:
|
||||
endpoint: http://ui/service/notifications/clair
|
||||
|
@ -35,7 +35,7 @@ services:
|
||||
networks:
|
||||
- harbor-clair
|
||||
container_name: clair
|
||||
image: quay.io/coreos/clair:v2.0.0
|
||||
image: quay.io/coreos/clair:v2.0.1
|
||||
restart: always
|
||||
depends_on:
|
||||
- postgres
|
||||
|
54
src/common/dao/clair.go
Normal file
54
src/common/dao/clair.go
Normal file
@ -0,0 +1,54 @@
|
||||
// copyright (c) 2017 vmware, inc. all rights reserved.
|
||||
//
|
||||
// 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 dao
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
//SetClairVulnTimestamp update the last_update of a namespace. If there's no record for this namespace, one will be created.
|
||||
func SetClairVulnTimestamp(namespace string, timestamp time.Time) error {
|
||||
o := GetOrmer()
|
||||
rec := &models.ClairVulnTimestamp{
|
||||
Namespace: namespace,
|
||||
LastUpdate: timestamp,
|
||||
}
|
||||
created, _, err := o.ReadOrCreate(rec, "Namespace")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !created {
|
||||
rec.LastUpdate = timestamp
|
||||
n, err := o.Update(rec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return fmt.Errorf("No record is updated, record: %v", *rec)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//ListClairVulnTimestamps return a list of all records in vuln timestamp table.
|
||||
func ListClairVulnTimestamps() ([]*models.ClairVulnTimestamp, error) {
|
||||
var res []*models.ClairVulnTimestamp
|
||||
o := GetOrmer()
|
||||
_, err := o.QueryTable(models.ClairVulnTimestampTable).All(&res)
|
||||
return res, err
|
||||
}
|
@ -1733,3 +1733,33 @@ func TestImgScanOverview(t *testing.T) {
|
||||
assert.Equal(int(models.SevMedium), res.Sev)
|
||||
assert.Equal(2, res.CompOverview.Summary[0].Count)
|
||||
}
|
||||
|
||||
func TestVulnTimestamp(t *testing.T) {
|
||||
|
||||
assert := assert.New(t)
|
||||
err := ClearTable(models.ClairVulnTimestampTable)
|
||||
assert.Nil(err)
|
||||
ns := "ubuntu:14"
|
||||
res, err := ListClairVulnTimestamps()
|
||||
assert.Nil(err)
|
||||
assert.Equal(0, len(res))
|
||||
err = SetClairVulnTimestamp(ns, time.Now())
|
||||
assert.Nil(err)
|
||||
res, err = ListClairVulnTimestamps()
|
||||
assert.Nil(err)
|
||||
assert.Equal(1, len(res))
|
||||
assert.Equal(ns, res[0].Namespace)
|
||||
old := time.Now()
|
||||
t.Logf("Sleep 3 seconds")
|
||||
time.Sleep(3 * time.Second)
|
||||
err = SetClairVulnTimestamp(ns, time.Now())
|
||||
assert.Nil(err)
|
||||
res, err = ListClairVulnTimestamps()
|
||||
assert.Nil(err)
|
||||
assert.Equal(1, len(res))
|
||||
|
||||
d := res[0].LastUpdate.Sub(old)
|
||||
if d < 2*time.Second {
|
||||
t.Errorf("Delta should be larger than 2 seconds! old: %v, lastupdate: %v", old, res[0].LastUpdate)
|
||||
}
|
||||
}
|
||||
|
@ -28,5 +28,6 @@ func init() {
|
||||
new(AccessLog),
|
||||
new(ScanJob),
|
||||
new(RepoRecord),
|
||||
new(ImgScanOverview))
|
||||
new(ImgScanOverview),
|
||||
new(ClairVulnTimestamp))
|
||||
}
|
||||
|
@ -14,6 +14,25 @@
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ClairVulnTimestampTable is the name of the table that tracks the timestamp of vulnerability in Clair.
|
||||
const ClairVulnTimestampTable = "clair_vuln_timestamp"
|
||||
|
||||
// ClairVulnTimestamp represents a record in DB that tracks the timestamp of vulnerability in Clair.
|
||||
type ClairVulnTimestamp struct {
|
||||
ID int64 `orm:"pk;auto;column(id)" json:"-"`
|
||||
Namespace string `orm:"column(namespace)" json:"namespace"`
|
||||
LastUpdate time.Time `orm:"column(last_update)" json:"last_update"`
|
||||
}
|
||||
|
||||
//TableName is required by beego to map struct to table.
|
||||
func (ct *ClairVulnTimestamp) TableName() string {
|
||||
return ClairVulnTimestampTable
|
||||
}
|
||||
|
||||
//ClairLayer ...
|
||||
type ClairLayer struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
@ -57,3 +76,34 @@ type ClairLayerEnvelope struct {
|
||||
Layer *ClairLayer `json:"Layer,omitempty"`
|
||||
Error *ClairError `json:"Error,omitempty"`
|
||||
}
|
||||
|
||||
//ClairNotification ...
|
||||
type ClairNotification struct {
|
||||
Name string `json:"Name,omitempty"`
|
||||
Created string `json:"Created,omitempty"`
|
||||
Notified string `json:"Notified,omitempty"`
|
||||
Deleted string `json:"Deleted,omitempty"`
|
||||
Limit int `json:"Limit,omitempty"`
|
||||
Page string `json:"Page,omitempty"`
|
||||
NextPage string `json:"NextPage,omitempty"`
|
||||
Old *ClairVulnerabilityWithLayers `json:"Old,omitempty"`
|
||||
New *ClairVulnerabilityWithLayers `json:"New,omitempty"`
|
||||
}
|
||||
|
||||
//ClairNotificationEnvelope ...
|
||||
type ClairNotificationEnvelope struct {
|
||||
Notification *ClairNotification `json:"Notification,omitempty"`
|
||||
Error *ClairError `json:"Error,omitempty"`
|
||||
}
|
||||
|
||||
//ClairVulnerabilityWithLayers ...
|
||||
type ClairVulnerabilityWithLayers struct {
|
||||
Vulnerability *ClairVulnerability `json:"Vulnerability,omitempty"`
|
||||
OrderedLayersIntroducingVulnerability []ClairOrderedLayerName `json:"OrderedLayersIntroducingVulnerability,omitempty"`
|
||||
}
|
||||
|
||||
//ClairOrderedLayerName ...
|
||||
type ClairOrderedLayerName struct {
|
||||
Index int `json:"Index"`
|
||||
LayerName string `json:"LayerName"`
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package admiral
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/security/authcontext"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
@ -41,7 +42,7 @@ func (s *SecurityContext) IsAuthenticated() bool {
|
||||
if s.ctx == nil {
|
||||
return false
|
||||
}
|
||||
return len(s.ctx.GetUsername()) > 0
|
||||
return len(s.ctx.PrincipalID) > 0
|
||||
}
|
||||
|
||||
// GetUsername returns the username of the authenticated user
|
||||
@ -50,7 +51,7 @@ func (s *SecurityContext) GetUsername() string {
|
||||
if !s.IsAuthenticated() {
|
||||
return ""
|
||||
}
|
||||
return s.ctx.GetUsername()
|
||||
return s.ctx.PrincipalID
|
||||
}
|
||||
|
||||
// IsSysAdmin returns whether the authenticated user is system admin
|
||||
@ -59,12 +60,12 @@ func (s *SecurityContext) IsSysAdmin() bool {
|
||||
if !s.IsAuthenticated() {
|
||||
return false
|
||||
}
|
||||
|
||||
return s.ctx.IsSysAdmin()
|
||||
}
|
||||
|
||||
// HasReadPerm returns whether the user has read permission to the project
|
||||
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
||||
// public project
|
||||
public, err := s.pm.IsPublic(projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the public of project %v: %v",
|
||||
@ -85,27 +86,9 @@ func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if name, ok := projectIDOrName.(string); ok {
|
||||
return s.ctx.HasReadPerm(name)
|
||||
}
|
||||
roles := s.GetProjectRoles(projectIDOrName)
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
common.RoleDeveloper,
|
||||
common.RoleGuest:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return len(roles) > 0
|
||||
}
|
||||
|
||||
// HasWritePerm returns whether the user has write permission to the project
|
||||
@ -119,17 +102,7 @@ func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if name, ok := projectIDOrName.(string); ok {
|
||||
return s.ctx.HasWritePerm(name)
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
roles := s.GetProjectRoles(projectIDOrName)
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
@ -152,17 +125,7 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if name, ok := projectIDOrName.(string); ok {
|
||||
return s.ctx.HasAllPerm(name)
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
roles := s.GetProjectRoles(projectIDOrName)
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin:
|
||||
@ -172,3 +135,17 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetMyProjects ...
|
||||
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
|
||||
return s.ctx.GetMyProjects(), nil
|
||||
}
|
||||
|
||||
// GetProjectRoles ...
|
||||
func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
||||
if !s.IsAuthenticated() || projectIDOrName == nil {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
return s.ctx.GetProjectRoles(projectIDOrName)
|
||||
}
|
||||
|
@ -21,9 +21,12 @@ import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
|
||||
// TODO update the value of role when admiral API is ready
|
||||
const (
|
||||
// AuthTokenHeader is the key of auth token header
|
||||
AuthTokenHeader = "x-xenon-auth-token"
|
||||
@ -48,70 +51,83 @@ type AuthContext struct {
|
||||
Projects []*project `json:"projects"`
|
||||
}
|
||||
|
||||
// GetUsername ...
|
||||
func (a *AuthContext) GetUsername() string {
|
||||
return a.PrincipalID
|
||||
}
|
||||
|
||||
// IsSysAdmin ...
|
||||
func (a *AuthContext) IsSysAdmin() bool {
|
||||
isSysAdmin := false
|
||||
for _, role := range a.Roles {
|
||||
if role == sysAdminRole {
|
||||
isSysAdmin = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return isSysAdmin
|
||||
}
|
||||
|
||||
// HasReadPerm ...
|
||||
func (a *AuthContext) HasReadPerm(projectName string) bool {
|
||||
roles := a.getRoles(projectName)
|
||||
return len(roles) > 0
|
||||
}
|
||||
|
||||
// HasWritePerm ...
|
||||
func (a *AuthContext) HasWritePerm(projectName string) bool {
|
||||
roles := a.getRoles(projectName)
|
||||
for _, role := range roles {
|
||||
if role == projectAdminRole || role == developerRole {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasAllPerm ...
|
||||
func (a *AuthContext) HasAllPerm(projectName string) bool {
|
||||
roles := a.getRoles(projectName)
|
||||
for _, role := range roles {
|
||||
if role == projectAdminRole {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// GetProjectRoles ...
|
||||
func (a *AuthContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
||||
var isID bool
|
||||
var id int64
|
||||
var name string
|
||||
|
||||
func (a *AuthContext) getRoles(projectName string) []string {
|
||||
id, isID = projectIDOrName.(int64)
|
||||
if !isID {
|
||||
name, _ = projectIDOrName.(string)
|
||||
}
|
||||
|
||||
roles := []string{}
|
||||
for _, project := range a.Projects {
|
||||
if project.Name == projectName {
|
||||
return project.Roles
|
||||
p := convertProject(project)
|
||||
if isID {
|
||||
if p.ProjectID == id {
|
||||
roles = append(roles, project.Roles...)
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if p.Name == name {
|
||||
roles = append(roles, project.Roles...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return []string{}
|
||||
return convertRoles(roles)
|
||||
}
|
||||
|
||||
// GetMyProjects returns all projects which the user is a member of
|
||||
func (a *AuthContext) GetMyProjects() []string {
|
||||
projects := []string{}
|
||||
func (a *AuthContext) GetMyProjects() []*models.Project {
|
||||
projects := []*models.Project{}
|
||||
for _, project := range a.Projects {
|
||||
projects = append(projects, project.Name)
|
||||
projects = append(projects, convertProject(project))
|
||||
}
|
||||
return projects
|
||||
}
|
||||
|
||||
// TODO populate harbor ID to the project
|
||||
// convert project returned by Admiral to project used in Harbor
|
||||
func convertProject(p *project) *models.Project {
|
||||
project := &models.Project{
|
||||
Name: p.Name,
|
||||
}
|
||||
return project
|
||||
}
|
||||
|
||||
// convert roles defined by Admiral to roles used in Harbor
|
||||
func convertRoles(roles []string) []int {
|
||||
list := []int{}
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case projectAdminRole:
|
||||
list = append(list, common.RoleProjectAdmin)
|
||||
case developerRole:
|
||||
list = append(list, common.RoleDeveloper)
|
||||
case guestRole:
|
||||
list = append(list, common.RoleGuest)
|
||||
default:
|
||||
log.Warningf("unknow role: %s", role)
|
||||
}
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
// GetAuthCtx returns the auth context of the current user
|
||||
func GetAuthCtx(client *http.Client, url, token string) (*AuthContext, error) {
|
||||
return get(client, url, token)
|
||||
|
@ -14,4 +14,61 @@
|
||||
|
||||
package authcontext
|
||||
|
||||
// TODO add test cases
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsSysAdmin(t *testing.T) {
|
||||
// nil roles
|
||||
ctx := &AuthContext{}
|
||||
assert.False(t, ctx.IsSysAdmin())
|
||||
|
||||
// has no admin role
|
||||
ctx = &AuthContext{
|
||||
Roles: []string{projectAdminRole, developerRole, guestRole},
|
||||
}
|
||||
assert.False(t, ctx.IsSysAdmin())
|
||||
|
||||
// has admin role
|
||||
ctx = &AuthContext{
|
||||
Roles: []string{sysAdminRole},
|
||||
}
|
||||
assert.True(t, ctx.IsSysAdmin())
|
||||
}
|
||||
|
||||
func TestGetProjectRoles(t *testing.T) {
|
||||
ctx := &AuthContext{
|
||||
Projects: []*project{
|
||||
&project{
|
||||
Name: "project",
|
||||
Roles: []string{projectAdminRole, developerRole, guestRole},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// test with name
|
||||
roles := ctx.GetProjectRoles("project")
|
||||
assert.Equal(t, 3, len(roles))
|
||||
|
||||
// TODO add test case with ID
|
||||
}
|
||||
|
||||
func TestGetMyProjects(t *testing.T) {
|
||||
ctx := &AuthContext{
|
||||
Projects: []*project{
|
||||
&project{
|
||||
Name: "project1",
|
||||
Roles: []string{projectAdminRole},
|
||||
},
|
||||
&project{
|
||||
Name: "project2",
|
||||
Roles: []string{developerRole},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
projects := ctx.GetMyProjects()
|
||||
assert.Equal(t, 2, len(projects))
|
||||
}
|
||||
|
@ -14,6 +14,10 @@
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
)
|
||||
|
||||
// Context abstracts the operations related with authN and authZ
|
||||
type Context interface {
|
||||
// IsAuthenticated returns whether the context has been authenticated or not
|
||||
@ -28,4 +32,6 @@ type Context interface {
|
||||
HasWritePerm(projectIDOrName interface{}) bool
|
||||
// HasAllPerm returns whether the user has all permissions to the project
|
||||
HasAllPerm(projectIDOrName interface{}) bool
|
||||
GetMyProjects() ([]*models.Project, error)
|
||||
GetProjectRoles(projectIDOrName interface{}) []int
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ package local
|
||||
|
||||
import (
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager"
|
||||
@ -60,18 +61,6 @@ func (s *SecurityContext) IsSysAdmin() bool {
|
||||
|
||||
// HasReadPerm returns whether the user has read permission to the project
|
||||
func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
||||
// not exist
|
||||
exist, err := s.pm.Exist(projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of project %v: %v",
|
||||
projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
|
||||
// public project
|
||||
public, err := s.pm.IsPublic(projectIDOrName)
|
||||
if err != nil {
|
||||
@ -93,23 +82,9 @@ func (s *SecurityContext) HasReadPerm(projectIDOrName interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
roles := s.GetProjectRoles(projectIDOrName)
|
||||
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
common.RoleDeveloper,
|
||||
common.RoleGuest:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
return len(roles) > 0
|
||||
}
|
||||
|
||||
// HasWritePerm returns whether the user has write permission to the project
|
||||
@ -118,30 +93,12 @@ func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// project does not exist
|
||||
exist, err := s.pm.Exist(projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of project %v: %v",
|
||||
projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
|
||||
// system admin
|
||||
if s.IsSysAdmin() {
|
||||
return true
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
roles := s.GetProjectRoles(projectIDOrName)
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin,
|
||||
@ -159,30 +116,12 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// project does not exist
|
||||
exist, err := s.pm.Exist(projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check the existence of project %v: %v",
|
||||
projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if !exist {
|
||||
return false
|
||||
}
|
||||
|
||||
// system admin
|
||||
if s.IsSysAdmin() {
|
||||
return true
|
||||
}
|
||||
|
||||
roles, err := s.pm.GetRoles(s.GetUsername(), projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %s to project %v: %v",
|
||||
s.GetUsername(), projectIDOrName, err)
|
||||
return false
|
||||
}
|
||||
|
||||
roles := s.GetProjectRoles(projectIDOrName)
|
||||
for _, role := range roles {
|
||||
switch role {
|
||||
case common.RoleProjectAdmin:
|
||||
@ -192,3 +131,61 @@ func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetMyProjects ...
|
||||
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
|
||||
return dao.GetProjects(&models.ProjectQueryParam{
|
||||
Member: &models.MemberQuery{
|
||||
Name: s.GetUsername(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// GetProjectRoles ...
|
||||
func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
||||
if !s.IsAuthenticated() || projectIDOrName == nil {
|
||||
return []int{}
|
||||
}
|
||||
|
||||
roles := []int{}
|
||||
user, err := dao.GetUser(models.User{
|
||||
Username: s.GetUsername(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("failed to get user %s: %v", s.GetUsername(), err)
|
||||
return roles
|
||||
}
|
||||
if user == nil {
|
||||
log.Debugf("user %s not found", s.GetUsername())
|
||||
return roles
|
||||
}
|
||||
|
||||
project, err := s.pm.Get(projectIDOrName)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get project %v: %v", projectIDOrName, err)
|
||||
return roles
|
||||
}
|
||||
if project == nil {
|
||||
log.Errorf("project %v not found", projectIDOrName)
|
||||
return roles
|
||||
}
|
||||
|
||||
roleList, err := dao.GetUserProjectRoles(user.UserID, project.ProjectID)
|
||||
if err != nil {
|
||||
log.Errorf("failed to get roles of user %d to project %d: %v", user.UserID, project.ProjectID, err)
|
||||
return roles
|
||||
}
|
||||
|
||||
for _, role := range roleList {
|
||||
switch role.RoleCode {
|
||||
case "MDRWS":
|
||||
roles = append(roles, common.RoleProjectAdmin)
|
||||
case "RWS":
|
||||
roles = append(roles, common.RoleDeveloper)
|
||||
case "RS":
|
||||
roles = append(roles, common.RoleGuest)
|
||||
}
|
||||
}
|
||||
|
||||
return roles
|
||||
}
|
||||
|
@ -15,109 +15,132 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/projectmanager/db"
|
||||
)
|
||||
|
||||
var (
|
||||
public = &models.Project{
|
||||
Name: "public_project",
|
||||
Public: 1,
|
||||
}
|
||||
|
||||
private = &models.Project{
|
||||
Name: "private_project",
|
||||
Public: 0,
|
||||
Name: "private_project",
|
||||
OwnerID: 1,
|
||||
}
|
||||
|
||||
read = &models.Project{
|
||||
Name: "has_read_perm_project",
|
||||
projectAdminUser = &models.User{
|
||||
Username: "projectAdminUser",
|
||||
Email: "projectAdminUser@vmware.com",
|
||||
}
|
||||
developerUser = &models.User{
|
||||
Username: "developerUser",
|
||||
Email: "developerUser@vmware.com",
|
||||
}
|
||||
guestUser = &models.User{
|
||||
Username: "guestUser",
|
||||
Email: "guestUser@vmware.com",
|
||||
}
|
||||
|
||||
write = &models.Project{
|
||||
Name: "has_write_perm_project",
|
||||
}
|
||||
|
||||
all = &models.Project{
|
||||
Name: "has_all_perm_project",
|
||||
}
|
||||
pm = &db.ProjectManager{}
|
||||
)
|
||||
|
||||
type fakePM struct {
|
||||
projects []*models.Project
|
||||
roles map[string][]int
|
||||
}
|
||||
|
||||
func (f *fakePM) IsPublic(projectIDOrName interface{}) (bool, error) {
|
||||
for _, project := range f.projects {
|
||||
if project.Name == projectIDOrName.(string) {
|
||||
return project.Public == 1, nil
|
||||
}
|
||||
func TestMain(m *testing.M) {
|
||||
dbHost := os.Getenv("MYSQL_HOST")
|
||||
if len(dbHost) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_HOST is not set")
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
func (f *fakePM) GetRoles(username string, projectIDOrName interface{}) ([]int, error) {
|
||||
return f.roles[projectIDOrName.(string)], nil
|
||||
}
|
||||
func (f *fakePM) Get(projectIDOrName interface{}) (*models.Project, error) {
|
||||
for _, project := range f.projects {
|
||||
if project.Name == projectIDOrName.(string) {
|
||||
return project, nil
|
||||
}
|
||||
dbPortStr := os.Getenv("MYSQL_PORT")
|
||||
if len(dbPortStr) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_PORT is not set")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakePM) Exist(projectIDOrName interface{}) (bool, error) {
|
||||
for _, project := range f.projects {
|
||||
if project.Name == projectIDOrName.(string) {
|
||||
return true, nil
|
||||
}
|
||||
dbPort, err := strconv.Atoi(dbPortStr)
|
||||
if err != nil {
|
||||
log.Fatalf("invalid MYSQL_PORT: %v", err)
|
||||
}
|
||||
dbUser := os.Getenv("MYSQL_USR")
|
||||
if len(dbUser) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_USR is not set")
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetPublic() ([]*models.Project, error) {
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
dbPassword := os.Getenv("MYSQL_PWD")
|
||||
dbDatabase := os.Getenv("MYSQL_DATABASE")
|
||||
if len(dbDatabase) == 0 {
|
||||
log.Fatalf("environment variable MYSQL_DATABASE is not set")
|
||||
}
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetByMember(username string) ([]*models.Project, error) {
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
database := &models.Database{
|
||||
Type: "mysql",
|
||||
MySQL: &models.MySQL{
|
||||
Host: dbHost,
|
||||
Port: dbPort,
|
||||
Username: dbUser,
|
||||
Password: dbPassword,
|
||||
Database: dbDatabase,
|
||||
},
|
||||
}
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) Create(*models.Project) (int64, error) {
|
||||
return 0, fmt.Errorf("not support")
|
||||
}
|
||||
log.Infof("MYSQL_HOST: %s, MYSQL_USR: %s, MYSQL_PORT: %d, MYSQL_PWD: %s\n", dbHost, dbUser, dbPort, dbPassword)
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) Delete(projectIDOrName interface{}) error {
|
||||
return fmt.Errorf("not support")
|
||||
}
|
||||
if err := dao.InitDatabase(database); err != nil {
|
||||
log.Fatalf("failed to initialize database: %v", err)
|
||||
}
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) Update(projectIDOrName interface{}, project *models.Project) error {
|
||||
return fmt.Errorf("not support")
|
||||
}
|
||||
// regiser users
|
||||
id, err := dao.Register(*projectAdminUser)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to register user: %v", err)
|
||||
}
|
||||
projectAdminUser.UserID = int(id)
|
||||
defer dao.DeleteUser(int(id))
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetAll(*models.ProjectQueryParam, ...*models.BaseProjectCollection) ([]*models.Project, error) {
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
id, err = dao.Register(*developerUser)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to register user: %v", err)
|
||||
}
|
||||
developerUser.UserID = int(id)
|
||||
defer dao.DeleteUser(int(id))
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetHasReadPerm(username ...string) ([]*models.Project, error) {
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
id, err = dao.Register(*guestUser)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to register user: %v", err)
|
||||
}
|
||||
guestUser.UserID = int(id)
|
||||
defer dao.DeleteUser(int(id))
|
||||
|
||||
// nil implement
|
||||
func (f *fakePM) GetTotal(*models.ProjectQueryParam, ...*models.BaseProjectCollection) (int64, error) {
|
||||
return 0, nil
|
||||
// add project
|
||||
id, err = dao.AddProject(*private)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to add project: %v", err)
|
||||
}
|
||||
private.ProjectID = id
|
||||
defer dao.DeleteProject(id)
|
||||
|
||||
// add project members
|
||||
err = dao.AddProjectMember(private.ProjectID, projectAdminUser.UserID, common.RoleProjectAdmin)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to add member: %v", err)
|
||||
}
|
||||
defer dao.DeleteProjectMember(private.ProjectID, projectAdminUser.UserID)
|
||||
|
||||
err = dao.AddProjectMember(private.ProjectID, developerUser.UserID, common.RoleDeveloper)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to add member: %v", err)
|
||||
}
|
||||
defer dao.DeleteProjectMember(private.ProjectID, developerUser.UserID)
|
||||
|
||||
err = dao.AddProjectMember(private.ProjectID, guestUser.UserID, common.RoleGuest)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to add member: %v", err)
|
||||
}
|
||||
defer dao.DeleteProjectMember(private.ProjectID, guestUser.UserID)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestIsAuthenticated(t *testing.T) {
|
||||
@ -164,147 +187,104 @@ func TestIsSysAdmin(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHasReadPerm(t *testing.T) {
|
||||
pm := &fakePM{
|
||||
projects: []*models.Project{public, private, read},
|
||||
roles: map[string][]int{
|
||||
"has_read_perm_project": []int{common.RoleGuest},
|
||||
},
|
||||
}
|
||||
|
||||
// non-exist project
|
||||
ctx := NewSecurityContext(nil, pm)
|
||||
assert.False(t, ctx.HasReadPerm("non_exist_project"))
|
||||
|
||||
// public project
|
||||
ctx = NewSecurityContext(nil, pm)
|
||||
assert.True(t, ctx.HasReadPerm("public_project"))
|
||||
ctx := NewSecurityContext(nil, pm)
|
||||
assert.True(t, ctx.HasReadPerm("library"))
|
||||
|
||||
// private project, unauthenticated
|
||||
ctx = NewSecurityContext(nil, pm)
|
||||
assert.False(t, ctx.HasReadPerm("private_project"))
|
||||
assert.False(t, ctx.HasReadPerm(private.Name))
|
||||
|
||||
// private project, authenticated, has no perm
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasReadPerm("private_project"))
|
||||
assert.False(t, ctx.HasReadPerm(private.Name))
|
||||
|
||||
// private project, authenticated, has read perm
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.True(t, ctx.HasReadPerm("has_read_perm_project"))
|
||||
ctx = NewSecurityContext(guestUser, pm)
|
||||
assert.True(t, ctx.HasReadPerm(private.Name))
|
||||
|
||||
// private project, authenticated, system admin
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
Username: "admin",
|
||||
HasAdminRole: 1,
|
||||
}, pm)
|
||||
assert.True(t, ctx.HasReadPerm("private_project"))
|
||||
|
||||
// non-exist project, authenticated, system admin
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
HasAdminRole: 1,
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasReadPerm("non_exist_project"))
|
||||
assert.True(t, ctx.HasReadPerm(private.Name))
|
||||
}
|
||||
|
||||
func TestHasWritePerm(t *testing.T) {
|
||||
pm := &fakePM{
|
||||
projects: []*models.Project{read, write, private},
|
||||
roles: map[string][]int{
|
||||
"has_read_perm_project": []int{common.RoleGuest},
|
||||
"has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper},
|
||||
},
|
||||
}
|
||||
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, pm)
|
||||
assert.False(t, ctx.HasWritePerm("has_write_perm_project"))
|
||||
|
||||
// authenticated, non-exist project
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasWritePerm("non_exist_project"))
|
||||
assert.False(t, ctx.HasWritePerm(private.Name))
|
||||
|
||||
// authenticated, has read perm
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasWritePerm("has_read_perm_project")) // authenticated, has read perm
|
||||
ctx = NewSecurityContext(guestUser, pm)
|
||||
assert.False(t, ctx.HasWritePerm(private.Name))
|
||||
|
||||
// authenticated, has write perm
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.True(t, ctx.HasWritePerm("has_write_perm_project"))
|
||||
ctx = NewSecurityContext(developerUser, pm)
|
||||
assert.True(t, ctx.HasWritePerm(private.Name))
|
||||
|
||||
// authenticated, system admin
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
Username: "admin",
|
||||
HasAdminRole: 1,
|
||||
}, pm)
|
||||
assert.True(t, ctx.HasReadPerm("private_project"))
|
||||
|
||||
// authenticated, system admin, non-exist project
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
HasAdminRole: 1,
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasReadPerm("non_exist_project"))
|
||||
assert.True(t, ctx.HasReadPerm(private.Name))
|
||||
}
|
||||
|
||||
func TestHasAllPerm(t *testing.T) {
|
||||
pm := &fakePM{
|
||||
projects: []*models.Project{read, write, all, private},
|
||||
roles: map[string][]int{
|
||||
"has_read_perm_project": []int{common.RoleGuest},
|
||||
"has_write_perm_project": []int{common.RoleGuest, common.RoleDeveloper},
|
||||
"has_all_perm_project": []int{common.RoleGuest, common.RoleDeveloper, common.RoleProjectAdmin},
|
||||
},
|
||||
}
|
||||
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, pm)
|
||||
assert.False(t, ctx.HasAllPerm("has_all_perm_project"))
|
||||
|
||||
// authenticated, non-exist project
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasAllPerm("non_exist_project"))
|
||||
|
||||
// authenticated, has read perm
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasAllPerm("has_read_perm_project"))
|
||||
|
||||
// authenticated, has write perm
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasAllPerm("has_write_perm_project"))
|
||||
assert.False(t, ctx.HasAllPerm(private.Name))
|
||||
|
||||
// authenticated, has all perms
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
}, pm)
|
||||
assert.True(t, ctx.HasAllPerm("has_all_perm_project"))
|
||||
ctx = NewSecurityContext(projectAdminUser, pm)
|
||||
assert.True(t, ctx.HasAllPerm(private.Name))
|
||||
|
||||
// authenticated, system admin
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
Username: "admin",
|
||||
HasAdminRole: 1,
|
||||
}, pm)
|
||||
assert.True(t, ctx.HasAllPerm("private_project"))
|
||||
|
||||
// authenticated, system admin, non-exist project
|
||||
ctx = NewSecurityContext(&models.User{
|
||||
Username: "test",
|
||||
HasAdminRole: 1,
|
||||
}, pm)
|
||||
assert.False(t, ctx.HasAllPerm("non_exist_project"))
|
||||
assert.True(t, ctx.HasAllPerm(private.Name))
|
||||
}
|
||||
|
||||
func TestGetMyProjects(t *testing.T) {
|
||||
ctx := NewSecurityContext(guestUser, pm)
|
||||
projects, err := ctx.GetMyProjects()
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, 1, len(projects))
|
||||
assert.Equal(t, private.ProjectID, projects[0].ProjectID)
|
||||
}
|
||||
|
||||
func TestGetProjectRoles(t *testing.T) {
|
||||
// unauthenticated
|
||||
ctx := NewSecurityContext(nil, pm)
|
||||
roles := ctx.GetProjectRoles(private.Name)
|
||||
assert.Equal(t, 0, len(roles))
|
||||
|
||||
// authenticated, project name of ID is nil
|
||||
ctx = NewSecurityContext(guestUser, pm)
|
||||
roles = ctx.GetProjectRoles(nil)
|
||||
assert.Equal(t, 0, len(roles))
|
||||
|
||||
// authenticated, has read perm
|
||||
ctx = NewSecurityContext(guestUser, pm)
|
||||
roles = ctx.GetProjectRoles(private.Name)
|
||||
assert.Equal(t, 1, len(roles))
|
||||
assert.Equal(t, common.RoleGuest, roles[0])
|
||||
|
||||
// authenticated, has write perm
|
||||
ctx = NewSecurityContext(developerUser, pm)
|
||||
roles = ctx.GetProjectRoles(private.Name)
|
||||
assert.Equal(t, 1, len(roles))
|
||||
assert.Equal(t, common.RoleDeveloper, roles[0])
|
||||
|
||||
// authenticated, has all perms
|
||||
ctx = NewSecurityContext(projectAdminUser, pm)
|
||||
roles = ctx.GetProjectRoles(private.Name)
|
||||
assert.Equal(t, 1, len(roles))
|
||||
assert.Equal(t, common.RoleProjectAdmin, roles[0])
|
||||
}
|
||||
|
@ -15,6 +15,10 @@
|
||||
package secret
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/secret"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
@ -79,3 +83,17 @@ func (s *SecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
||||
func (s *SecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetMyProjects ...
|
||||
func (s *SecurityContext) GetMyProjects() ([]*models.Project, error) {
|
||||
return nil, fmt.Errorf("GetMyProjects is unsupported")
|
||||
}
|
||||
|
||||
// GetProjectRoles return guest role if has read permission, otherwise return nil
|
||||
func (s *SecurityContext) GetProjectRoles(projectIDOrName interface{}) []int {
|
||||
roles := []int{}
|
||||
if s.HasReadPerm(projectIDOrName) {
|
||||
roles = append(roles, common.RoleGuest)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/secret"
|
||||
)
|
||||
|
||||
@ -132,3 +133,34 @@ func TestHasAllPerm(t *testing.T) {
|
||||
hasAllPerm = context.HasAllPerm(1)
|
||||
assert.False(t, hasAllPerm)
|
||||
}
|
||||
|
||||
func TestGetMyProjects(t *testing.T) {
|
||||
context := NewSecurityContext("secret",
|
||||
secret.NewStore(map[string]string{
|
||||
"secret": "username",
|
||||
}))
|
||||
|
||||
_, err := context.GetMyProjects()
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestGetProjectRoles(t *testing.T) {
|
||||
//invalid secret
|
||||
context := NewSecurityContext("invalid_secret",
|
||||
secret.NewStore(map[string]string{
|
||||
"jobservice_secret": secret.JobserviceUser,
|
||||
}))
|
||||
|
||||
roles := context.GetProjectRoles("any_project")
|
||||
assert.Equal(t, 0, len(roles))
|
||||
|
||||
// valid secret
|
||||
context = NewSecurityContext("jobservice_secret",
|
||||
secret.NewStore(map[string]string{
|
||||
"jobservice_secret": secret.JobserviceUser,
|
||||
}))
|
||||
|
||||
roles = context.GetProjectRoles("any_project")
|
||||
assert.Equal(t, 1, len(roles))
|
||||
assert.Equal(t, common.RoleGuest, roles[0])
|
||||
}
|
||||
|
@ -104,3 +104,55 @@ func (c *Client) GetResult(layerName string) (*models.ClairLayerEnvelope, error)
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
// GetNotification calls Clair's API to get details of notification
|
||||
func (c *Client) GetNotification(id string) (*models.ClairNotification, error) {
|
||||
req, err := http.NewRequest("GET", c.endpoint+"/v1/notifications/"+id+"?limit=2", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b))
|
||||
}
|
||||
var ne models.ClairNotificationEnvelope
|
||||
err = json.Unmarshal(b, &ne)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ne.Error != nil {
|
||||
return nil, fmt.Errorf("Clair error: %s", ne.Error.Message)
|
||||
}
|
||||
log.Debugf("Retrived notification %s from Clair.", id)
|
||||
return ne.Notification, nil
|
||||
}
|
||||
|
||||
// DeleteNotification deletes a notification record from Clair
|
||||
func (c *Client) DeleteNotification(id string) error {
|
||||
req, err := http.NewRequest("DELETE", c.endpoint+"/v1/notifications/"+id, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("Unexpected status code: %d, text: %s", resp.StatusCode, string(b))
|
||||
}
|
||||
log.Debugf("Deleted notification %s from Clair.", id)
|
||||
return nil
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func (l *LogAPI) Get() {
|
||||
}
|
||||
|
||||
if !l.isSysAdmin {
|
||||
projects, err := l.ProjectMgr.GetByMember(l.username)
|
||||
projects, err := l.SecurityCtx.GetMyProjects()
|
||||
if err != nil {
|
||||
l.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects of user %s: %v", l.username, err))
|
||||
|
@ -311,13 +311,7 @@ func (p *ProjectAPI) List() {
|
||||
|
||||
for _, project := range projects {
|
||||
if p.SecurityCtx.IsAuthenticated() {
|
||||
roles, err := p.ProjectMgr.GetRoles(p.SecurityCtx.GetUsername(), project.ProjectID)
|
||||
if err != nil {
|
||||
p.HandleInternalServerError(fmt.Sprintf("failed to get roles of user %s to project %d: %v",
|
||||
p.SecurityCtx.GetUsername(), project.ProjectID, err))
|
||||
return
|
||||
}
|
||||
|
||||
roles := p.SecurityCtx.GetProjectRoles(project.ProjectID)
|
||||
if len(roles) != 0 {
|
||||
project.Role = roles[0]
|
||||
}
|
||||
|
@ -322,18 +322,8 @@ func (ra *RepositoryAPI) GetTag() {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := assemble(client, repository, []string{tag},
|
||||
result := assemble(client, repository, []string{tag},
|
||||
ra.SecurityCtx.GetUsername())
|
||||
if err != nil {
|
||||
regErr, ok := err.(*registry_error.Error)
|
||||
if !ok {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to get tag %s of %s: %v", tag, repository, err))
|
||||
return
|
||||
}
|
||||
ra.RenderError(regErr.StatusCode, regErr.Detail)
|
||||
return
|
||||
}
|
||||
|
||||
ra.Data["json"] = result[0]
|
||||
ra.ServeJSON()
|
||||
}
|
||||
@ -376,112 +366,95 @@ func (ra *RepositoryAPI) GetTags() {
|
||||
return
|
||||
}
|
||||
|
||||
result, err := assemble(client, repoName, tags, ra.SecurityCtx.GetUsername())
|
||||
if err != nil {
|
||||
regErr, ok := err.(*registry_error.Error)
|
||||
if !ok {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to get tag of %s: %v", repoName, err))
|
||||
return
|
||||
}
|
||||
ra.RenderError(regErr.StatusCode, regErr.Detail)
|
||||
return
|
||||
}
|
||||
|
||||
ra.Data["json"] = result
|
||||
ra.Data["json"] = assemble(client, repoName, tags, ra.SecurityCtx.GetUsername())
|
||||
ra.ServeJSON()
|
||||
}
|
||||
|
||||
// get config, signature and scan overview and assemble them into one
|
||||
// struct for each tag in tags
|
||||
func assemble(client *registry.Repository, repository string,
|
||||
tags []string, username string) ([]*tagResp, error) {
|
||||
// get configs
|
||||
list, err := getDetailedTags(client, tags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tags []string, username string) []*tagResp {
|
||||
|
||||
// get signatures
|
||||
var err error
|
||||
signatures := map[string]*notary.Target{}
|
||||
if config.WithNotary() {
|
||||
signatures, err = getSignatures(repository, username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
signatures = map[string]*notary.Target{}
|
||||
log.Errorf("failed to get signatures of %s: %v", repository, err)
|
||||
}
|
||||
}
|
||||
|
||||
// assemble the response
|
||||
result := []*tagResp{}
|
||||
for _, tag := range list {
|
||||
item := &tagResp{
|
||||
tag: *tag,
|
||||
for _, t := range tags {
|
||||
item := &tagResp{}
|
||||
|
||||
// tag configuration
|
||||
digest, _, cfg, err := getV2Manifest(client, t)
|
||||
if err != nil {
|
||||
cfg = &tag{
|
||||
Digest: digest,
|
||||
Name: t,
|
||||
}
|
||||
log.Errorf("failed to get v2 manifest of %s:%s: %v", repository, t, err)
|
||||
}
|
||||
if cfg != nil {
|
||||
item.tag = *cfg
|
||||
}
|
||||
|
||||
// scan overview
|
||||
if config.WithClair() {
|
||||
item.ScanOverview = getScanOverview(item.Digest, item.Name)
|
||||
}
|
||||
|
||||
// compare both digest and tag
|
||||
if signature, ok := signatures[item.Digest]; ok {
|
||||
if item.Name == signature.Tag {
|
||||
item.Signature = signature
|
||||
// signature, compare both digest and tag
|
||||
if config.WithNotary() {
|
||||
if signature, ok := signatures[item.Digest]; ok {
|
||||
if item.Name == signature.Tag {
|
||||
item.Signature = signature
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, item)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// get tags of the repository, read manifest for every tag
|
||||
// and assemble necessary attrs(os, architecture, etc.) into
|
||||
// one struct
|
||||
func getDetailedTags(client *registry.Repository, tags []string) ([]*tag, error) {
|
||||
list := []*tag{}
|
||||
for _, t := range tags {
|
||||
// the ignored manifest can be used to calculate the image size
|
||||
digest, _, config, err := getV2Manifest(client, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag := &tag{}
|
||||
if err = json.Unmarshal(config, tag); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag.Name = t
|
||||
tag.Digest = digest
|
||||
|
||||
list = append(list, tag)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
return result
|
||||
}
|
||||
|
||||
// get v2 manifest of tag, returns digest, manifest,
|
||||
// manifest config and error. The manifest config contains
|
||||
// architecture, os, author, etc.
|
||||
func getV2Manifest(client *registry.Repository, tag string) (
|
||||
string, *schema2.DeserializedManifest, []byte, error) {
|
||||
digest, _, payload, err := client.PullManifest(tag, []string{schema2.MediaTypeManifest})
|
||||
func getV2Manifest(client *registry.Repository, tagName string) (
|
||||
string, *schema2.DeserializedManifest, *tag, error) {
|
||||
digest, _, payload, err := client.PullManifest(tagName, []string{schema2.MediaTypeManifest})
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
|
||||
manifest := &schema2.DeserializedManifest{}
|
||||
if err = manifest.UnmarshalJSON(payload); err != nil {
|
||||
return "", nil, nil, err
|
||||
return digest, nil, nil, err
|
||||
}
|
||||
|
||||
_, reader, err := client.PullBlob(manifest.Target().Digest.String())
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
return digest, manifest, nil, err
|
||||
}
|
||||
|
||||
config, err := ioutil.ReadAll(reader)
|
||||
configData, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
return "", nil, nil, err
|
||||
return digest, manifest, nil, err
|
||||
}
|
||||
|
||||
config := &tag{}
|
||||
if err = json.Unmarshal(configData, config); err != nil {
|
||||
return digest, manifest, nil, err
|
||||
}
|
||||
|
||||
config.Name = tagName
|
||||
config.Digest = digest
|
||||
|
||||
return digest, manifest, config, nil
|
||||
}
|
||||
|
||||
@ -608,7 +581,7 @@ func (ra *RepositoryAPI) GetTopRepos() {
|
||||
return
|
||||
}
|
||||
if ra.SecurityCtx.IsAuthenticated() {
|
||||
list, err := ra.ProjectMgr.GetByMember(ra.SecurityCtx.GetUsername())
|
||||
list, err := ra.SecurityCtx.GetMyProjects()
|
||||
if err != nil {
|
||||
ra.HandleInternalServerError(fmt.Sprintf("failed to get projects which the user %s is a member of: %v",
|
||||
ra.SecurityCtx.GetUsername(), err))
|
||||
@ -826,6 +799,10 @@ func (ra *RepositoryAPI) checkExistence(repository, tag string) (bool, string, e
|
||||
|
||||
//will return nil when it failed to get data. The parm "tag" is for logging only.
|
||||
func getScanOverview(digest string, tag string) *models.ImgScanOverview {
|
||||
if len(digest) == 0 {
|
||||
log.Debug("digest is nil")
|
||||
return nil
|
||||
}
|
||||
data, err := dao.GetImgScanOverview(digest)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get scan result for tag:%s, digest: %s, error: %v", tag, digest, err)
|
||||
|
@ -43,23 +43,43 @@ type searchResult struct {
|
||||
func (s *SearchAPI) Get() {
|
||||
keyword := s.GetString("q")
|
||||
isAuthenticated := s.SecurityCtx.IsAuthenticated()
|
||||
username := s.SecurityCtx.GetUsername()
|
||||
isSysAdmin := s.SecurityCtx.IsSysAdmin()
|
||||
|
||||
var projects []*models.Project
|
||||
var err error
|
||||
|
||||
if !isAuthenticated {
|
||||
projects, err = s.ProjectMgr.GetPublic()
|
||||
} else if isSysAdmin {
|
||||
if isSysAdmin {
|
||||
projects, err = s.ProjectMgr.GetAll(nil)
|
||||
if err != nil {
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects: %v", err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
projects, err = s.ProjectMgr.GetHasReadPerm(username)
|
||||
}
|
||||
if err != nil {
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects: %v", err))
|
||||
return
|
||||
projects, err = s.ProjectMgr.GetPublic()
|
||||
if err != nil {
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects: %v", err))
|
||||
return
|
||||
}
|
||||
if isAuthenticated {
|
||||
mys, err := s.SecurityCtx.GetMyProjects()
|
||||
if err != nil {
|
||||
s.HandleInternalServerError(fmt.Sprintf(
|
||||
"failed to get projects: %v", err))
|
||||
return
|
||||
}
|
||||
exist := map[int64]bool{}
|
||||
for _, p := range projects {
|
||||
exist[p.ProjectID] = true
|
||||
}
|
||||
|
||||
for _, p := range mys {
|
||||
if !exist[p.ProjectID] {
|
||||
projects = append(projects, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
projectSorter := &models.ProjectSorter{Projects: projects}
|
||||
@ -71,13 +91,7 @@ func (s *SearchAPI) Get() {
|
||||
}
|
||||
|
||||
if isAuthenticated {
|
||||
roles, err := s.ProjectMgr.GetRoles(username, p.ProjectID)
|
||||
if err != nil {
|
||||
s.HandleInternalServerError(fmt.Sprintf("failed to get roles of user %s to project %d: %v",
|
||||
username, p.ProjectID, err))
|
||||
return
|
||||
}
|
||||
|
||||
roles := s.SecurityCtx.GetProjectRoles(p.ProjectID)
|
||||
if len(roles) != 0 {
|
||||
p.Role = roles[0]
|
||||
}
|
||||
|
@ -374,7 +374,7 @@ func commonValidate(user models.User) error {
|
||||
return fmt.Errorf("Email can't be empty")
|
||||
}
|
||||
|
||||
if isIllegalLength(user.Realname, 1, 20) {
|
||||
if isIllegalLength(user.Realname, 1, 255) {
|
||||
return fmt.Errorf("realname with illegal length")
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
)
|
||||
@ -62,48 +61,6 @@ func (p *ProjectManager) IsPublic(projectIDOrName interface{}) (bool, error) {
|
||||
return project.Public == 1, nil
|
||||
}
|
||||
|
||||
// GetRoles return a role list which contains the user's roles to the project
|
||||
func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{}) ([]int, error) {
|
||||
roles := []int{}
|
||||
|
||||
user, err := dao.GetUser(models.User{
|
||||
Username: username,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get user %s: %v",
|
||||
username, err)
|
||||
}
|
||||
if user == nil {
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
project, err := p.Get(projectIDOrName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if project == nil {
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
roleList, err := dao.GetUserProjectRoles(user.UserID, project.ProjectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, role := range roleList {
|
||||
switch role.RoleCode {
|
||||
case "MDRWS":
|
||||
roles = append(roles, common.RoleProjectAdmin)
|
||||
case "RWS":
|
||||
roles = append(roles, common.RoleDeveloper)
|
||||
case "RS":
|
||||
roles = append(roles, common.RoleGuest)
|
||||
}
|
||||
}
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
// GetPublic returns all public projects
|
||||
func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
|
||||
t := true
|
||||
@ -112,20 +69,6 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetByMember returns all projects which the user is a member of
|
||||
func (p *ProjectManager) GetByMember(username string) (
|
||||
[]*models.Project, error) {
|
||||
if len(username) == 0 {
|
||||
return []*models.Project{}, nil
|
||||
}
|
||||
|
||||
return p.GetAll(&models.ProjectQueryParam{
|
||||
Member: &models.MemberQuery{
|
||||
Name: username,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (p *ProjectManager) Create(project *models.Project) (int64, error) {
|
||||
if project == nil {
|
||||
@ -204,13 +147,3 @@ func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*mode
|
||||
int64, error) {
|
||||
return dao.GetTotalOfProjects(query, base...)
|
||||
}
|
||||
|
||||
// GetHasReadPerm returns projects which are public or the user is a member of
|
||||
func (p *ProjectManager) GetHasReadPerm(username ...string) (
|
||||
[]*models.Project, error) {
|
||||
if len(username) == 0 || len(username[0]) == 0 {
|
||||
return p.GetPublic()
|
||||
}
|
||||
|
||||
return dao.GetHasReadPermProjects(username[0])
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
@ -121,25 +120,6 @@ func TestIsPublic(t *testing.T) {
|
||||
assert.False(t, public)
|
||||
}
|
||||
|
||||
func TestGetRoles(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
|
||||
// non exist user
|
||||
roles, err := pm.GetRoles("non_exist_user", int64(1))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []int{}, roles)
|
||||
|
||||
// exist project
|
||||
roles, err = pm.GetRoles("admin", "library")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []int{common.RoleProjectAdmin}, roles)
|
||||
|
||||
// non-exist project
|
||||
roles, err = pm.GetRoles("admin", "non_exist_project")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, []int{}, roles)
|
||||
}
|
||||
|
||||
func TestGetPublic(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
projects, err := pm.GetPublic()
|
||||
@ -151,19 +131,6 @@ func TestGetPublic(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetByMember(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
// empty username
|
||||
projects, err := pm.GetByMember("")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 0, len(projects))
|
||||
|
||||
//non-empty username
|
||||
projects, err = pm.GetByMember("admin")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
}
|
||||
|
||||
func TestCreateAndDelete(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
|
||||
@ -304,58 +271,3 @@ func TestGetAll(t *testing.T) {
|
||||
}
|
||||
assert.True(t, exist)
|
||||
}
|
||||
|
||||
func TestGetHasReadPerm(t *testing.T) {
|
||||
pm := &ProjectManager{}
|
||||
|
||||
// do not pass username
|
||||
projects, err := pm.GetHasReadPerm()
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
exist := false
|
||||
for _, project := range projects {
|
||||
if project.ProjectID == 1 {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, exist)
|
||||
|
||||
// username is nil
|
||||
projects, err = pm.GetHasReadPerm("")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
exist = false
|
||||
for _, project := range projects {
|
||||
if project.ProjectID == 1 {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, exist)
|
||||
|
||||
// valid username
|
||||
id, err := pm.Create(&models.Project{
|
||||
Name: "get_has_read_perm_test",
|
||||
OwnerID: 1,
|
||||
Public: 0,
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
defer pm.Delete(id)
|
||||
|
||||
projects, err = pm.GetHasReadPerm("admin")
|
||||
assert.Nil(t, err)
|
||||
assert.NotEqual(t, 0, len(projects))
|
||||
exist1 := false
|
||||
exist2 := false
|
||||
for _, project := range projects {
|
||||
if project.ProjectID == 1 {
|
||||
exist1 = true
|
||||
}
|
||||
if project.ProjectID == id {
|
||||
exist2 = true
|
||||
}
|
||||
}
|
||||
assert.True(t, exist1)
|
||||
assert.True(t, exist2)
|
||||
}
|
||||
|
@ -24,11 +24,8 @@ type ProjectManager interface {
|
||||
Get(projectIDOrName interface{}) (*models.Project, error)
|
||||
IsPublic(projectIDOrName interface{}) (bool, error)
|
||||
Exist(projectIDOrName interface{}) (bool, error)
|
||||
GetRoles(username string, projectIDOrName interface{}) ([]int, error)
|
||||
// get all public project
|
||||
GetPublic() ([]*models.Project, error)
|
||||
// get projects which the user is a member of
|
||||
GetByMember(username string) ([]*models.Project, error)
|
||||
Create(*models.Project) (int64, error)
|
||||
Delete(projectIDOrName interface{}) error
|
||||
Update(projectIDOrName interface{}, project *models.Project) error
|
||||
@ -36,8 +33,4 @@ type ProjectManager interface {
|
||||
GetAll(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) ([]*models.Project, error)
|
||||
// GetTotal returns the total count according to the query parameters
|
||||
GetTotal(query *models.ProjectQueryParam, base ...*models.BaseProjectCollection) (int64, error)
|
||||
// GetHasReadPerm returns a project list which the user has read
|
||||
// permission of. The list should contains all public projects and
|
||||
// projects which the user is a member of if the username is not nil
|
||||
GetHasReadPerm(username ...string) ([]*models.Project, error)
|
||||
}
|
||||
|
@ -25,9 +25,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/harbor/src/common"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/security/authcontext"
|
||||
er "github.com/vmware/harbor/src/common/utils/error"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
)
|
||||
@ -227,6 +225,7 @@ func (p *ProjectManager) Exist(projectIDOrName interface{}) (bool, error) {
|
||||
return project != nil, nil
|
||||
}
|
||||
|
||||
/*
|
||||
// GetRoles gets roles that the user has to the project
|
||||
// This method is used in GET /projects API.
|
||||
// Jobservice calls GET /projects API to get information of source
|
||||
@ -279,6 +278,7 @@ func (p *ProjectManager) GetRoles(username string, projectIDOrName interface{})
|
||||
|
||||
return roles, nil
|
||||
}
|
||||
*/
|
||||
|
||||
func (p *ProjectManager) getIDbyHarborIDOrName(projectIDOrName interface{}) (string, error) {
|
||||
pro, err := p.get(projectIDOrName)
|
||||
@ -301,26 +301,6 @@ func (p *ProjectManager) GetPublic() ([]*models.Project, error) {
|
||||
})
|
||||
}
|
||||
|
||||
// GetByMember ...
|
||||
func (p *ProjectManager) GetByMember(username string) ([]*models.Project, error) {
|
||||
projects := []*models.Project{}
|
||||
ctx, err := authcontext.GetAuthCtxOfUser(p.client, p.endpoint, p.getToken(), username)
|
||||
if err != nil {
|
||||
return projects, err
|
||||
}
|
||||
|
||||
names := ctx.GetMyProjects()
|
||||
for _, name := range names {
|
||||
project, err := p.Get(name)
|
||||
if err != nil {
|
||||
return projects, err
|
||||
}
|
||||
projects = append(projects, project)
|
||||
}
|
||||
|
||||
return projects, nil
|
||||
}
|
||||
|
||||
// Create ...
|
||||
func (p *ProjectManager) Create(pro *models.Project) (int64, error) {
|
||||
proj := &project{
|
||||
@ -407,11 +387,6 @@ func (p *ProjectManager) GetTotal(query *models.ProjectQueryParam, base ...*mode
|
||||
return int64(len(projects)), err
|
||||
}
|
||||
|
||||
// GetHasReadPerm ...
|
||||
func (p *ProjectManager) GetHasReadPerm(username ...string) ([]*models.Project, error) {
|
||||
return nil, errors.New("GetHasReadPerm is unsupported")
|
||||
}
|
||||
|
||||
func (p *ProjectManager) send(method, path string, body io.Reader) ([]byte, error) {
|
||||
req, err := http.NewRequest(method, p.endpoint+path, body)
|
||||
if err != nil {
|
||||
|
@ -292,33 +292,6 @@ func TestExist(t *testing.T) {
|
||||
assert.True(t, exist)
|
||||
}
|
||||
|
||||
func TestGetRoles(t *testing.T) {
|
||||
pm := NewProjectManager(client, endpoint, tokenReader)
|
||||
|
||||
// nil username, nil project
|
||||
roles, err := pm.GetRoles("", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Zero(t, len(roles))
|
||||
|
||||
// non-exist project
|
||||
_, err = pm.GetRoles("user01", "non_exist_project")
|
||||
assert.NotNil(t, err)
|
||||
|
||||
// exist project
|
||||
name := "project_for_test_get_roles"
|
||||
id, err := pm.Create(&models.Project{
|
||||
Name: name,
|
||||
})
|
||||
require.Nil(t, err)
|
||||
defer delete(t, id)
|
||||
|
||||
roles, err = pm.GetRoles("user01", id)
|
||||
assert.Nil(t, err)
|
||||
assert.Zero(t, len(roles))
|
||||
|
||||
// TODO add test cases for real role of user
|
||||
}
|
||||
|
||||
func TestGetPublic(t *testing.T) {
|
||||
pm := NewProjectManager(client, endpoint, tokenReader)
|
||||
|
||||
@ -348,11 +321,6 @@ func TestGetPublic(t *testing.T) {
|
||||
assert.True(t, found)
|
||||
}
|
||||
|
||||
// TODO add test case
|
||||
func TestGetByMember(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
pm := NewProjectManager(client, endpoint, tokenReader)
|
||||
|
||||
@ -492,12 +460,6 @@ func TestGetTotal(t *testing.T) {
|
||||
assert.Equal(t, total1+1, total2)
|
||||
}
|
||||
|
||||
func TestGetHasReadPerm(t *testing.T) {
|
||||
pm := NewProjectManager(client, endpoint, tokenReader)
|
||||
_, err := pm.GetHasReadPerm()
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func delete(t *testing.T, id int64) {
|
||||
pm := NewProjectManager(client, endpoint, tokenReader)
|
||||
if err := pm.Delete(id); err != nil {
|
||||
|
@ -17,7 +17,8 @@ package main
|
||||
import (
|
||||
"github.com/vmware/harbor/src/ui/api"
|
||||
"github.com/vmware/harbor/src/ui/controllers"
|
||||
"github.com/vmware/harbor/src/ui/service"
|
||||
"github.com/vmware/harbor/src/ui/service/notifications/clair"
|
||||
"github.com/vmware/harbor/src/ui/service/notifications/registry"
|
||||
"github.com/vmware/harbor/src/ui/service/token"
|
||||
|
||||
"github.com/astaxie/beego"
|
||||
@ -109,7 +110,8 @@ func initRouters() {
|
||||
beego.Router("/api/email/ping", &api.EmailAPI{}, "post:Ping")
|
||||
|
||||
//external service that hosted on harbor process:
|
||||
beego.Router("/service/notifications", &service.NotificationHandler{})
|
||||
beego.Router("/service/notifications", ®istry.NotificationHandler{})
|
||||
beego.Router("/service/notifications/clair", &clair.Handler{}, "post:Handle")
|
||||
beego.Router("/service/token", &token.Handler{})
|
||||
|
||||
beego.Router("/registryproxy/*", &controllers.RegistryProxy{}, "*:Handle")
|
||||
|
109
src/ui/service/notifications/clair/handler.go
Normal file
109
src/ui/service/notifications/clair/handler.go
Normal file
@ -0,0 +1,109 @@
|
||||
// Copyright (c) 2017 VMware, Inc. All Rights Reserved.
|
||||
//
|
||||
// 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 clair
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/harbor/src/common/dao"
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/clair"
|
||||
"github.com/vmware/harbor/src/common/utils/log"
|
||||
"github.com/vmware/harbor/src/ui/api"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
|
||||
const (
|
||||
rescanInterval = 15 * time.Minute
|
||||
)
|
||||
|
||||
type timer struct {
|
||||
sync.Mutex
|
||||
next time.Time
|
||||
}
|
||||
|
||||
// returns true to indicate it should reshedule the "rescan" action.
|
||||
func (t *timer) needReschedule() bool {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if time.Now().Before(t.next) {
|
||||
return false
|
||||
}
|
||||
t.next = time.Now().Add(rescanInterval)
|
||||
return true
|
||||
}
|
||||
|
||||
var (
|
||||
rescanTimer = timer{}
|
||||
clairClient = clair.NewClient(config.ClairEndpoint(), nil)
|
||||
)
|
||||
|
||||
// Handler handles reqeust on /service/notifications/clair/, which listens to clair's notifications.
|
||||
// When there's unexpected error it will silently fail without removing the notification such that it will be triggered again.
|
||||
type Handler struct {
|
||||
api.BaseController
|
||||
}
|
||||
|
||||
// Handle ...
|
||||
func (h *Handler) Handle() {
|
||||
var ne models.ClairNotificationEnvelope
|
||||
if err := json.Unmarshal(h.Ctx.Input.CopyBody(1<<32), &ne); err != nil {
|
||||
log.Errorf("Failed to decode the request: %v", err)
|
||||
return
|
||||
}
|
||||
log.Debugf("Received notification from Clair, name: %s", ne.Notification.Name)
|
||||
notification, err := clairClient.GetNotification(ne.Notification.Name)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to get notification details from Clair, name: %s, err: %v", ne.Notification.Name, err)
|
||||
return
|
||||
}
|
||||
ns := make(map[string]bool)
|
||||
if old := notification.Old; old != nil {
|
||||
if vuln := old.Vulnerability; vuln != nil {
|
||||
log.Debugf("old vulnerability namespace: %s", vuln.NamespaceName)
|
||||
ns[vuln.NamespaceName] = true
|
||||
}
|
||||
}
|
||||
if new := notification.New; new != nil {
|
||||
if vuln := new.Vulnerability; vuln != nil {
|
||||
log.Debugf("new vulnerability namespace: %s", vuln.NamespaceName)
|
||||
ns[vuln.NamespaceName] = true
|
||||
}
|
||||
}
|
||||
for k, v := range ns {
|
||||
if v {
|
||||
if err := dao.SetClairVulnTimestamp(k, time.Now()); err == nil {
|
||||
log.Debugf("Updated the timestamp for namespaces: %s", k)
|
||||
} else {
|
||||
log.Warningf("Failed to update the timestamp for namespaces: %s, error: %v", k, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if rescanTimer.needReschedule() {
|
||||
go func() {
|
||||
<-time.After(rescanInterval)
|
||||
log.Debugf("TODO: rescan or resfresh scan_overview!")
|
||||
}()
|
||||
} else {
|
||||
log.Debugf("There is a rescan scheduled already, skip.")
|
||||
}
|
||||
if err := clairClient.DeleteNotification(ne.Notification.Name); err != nil {
|
||||
log.Warningf("Failed to remove notification from Clair, name: %s", ne.Notification.Name)
|
||||
} else {
|
||||
log.Debugf("Removed notification from Clair, name: %s", ne.Notification.Name)
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package service
|
||||
package registry
|
||||
|
||||
import (
|
||||
"encoding/json"
|
@ -29,6 +29,7 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/vmware/harbor/src/common/models"
|
||||
"github.com/vmware/harbor/src/common/utils/test"
|
||||
"github.com/vmware/harbor/src/ui/config"
|
||||
)
|
||||
@ -225,6 +226,12 @@ func (f *fakeSecurityContext) HasWritePerm(projectIDOrName interface{}) bool {
|
||||
func (f *fakeSecurityContext) HasAllPerm(projectIDOrName interface{}) bool {
|
||||
return false
|
||||
}
|
||||
func (f *fakeSecurityContext) GetMyProjects() ([]*models.Project, error) {
|
||||
return nil, nil
|
||||
}
|
||||
func (f *fakeSecurityContext) GetProjectRoles(interface{}) []int {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestFilterAccess(t *testing.T) {
|
||||
//TODO put initial data in DB to verify repository filter.
|
||||
|
@ -44,4 +44,5 @@ Changelog for harbor database schema
|
||||
- delete column `user_id` from table `access_log`
|
||||
- delete foreign key (user_id) references user(user_id)from table `access_log`
|
||||
- delete foreign key (project_id) references project(project_id)from table `access_log`
|
||||
- add column `username` varchar (32) to table `access_log`
|
||||
- add column `username` varchar (32) to table `access_log`
|
||||
- alter column `realname` on table `user`: varchar(20)->varchar(255)
|
@ -15,7 +15,7 @@ class User(Base):
|
||||
username = sa.Column(sa.String(15), unique=True)
|
||||
email = sa.Column(sa.String(30), unique=True)
|
||||
password = sa.Column(sa.String(40), nullable=False)
|
||||
realname = sa.Column(sa.String(20), nullable=False)
|
||||
realname = sa.Column(sa.String(255), nullable=False)
|
||||
comment = sa.Column(sa.String(30))
|
||||
deleted = sa.Column(sa.Integer, nullable=False, server_default=sa.text("'0'"))
|
||||
reset_uuid = sa.Column(sa.String(40))
|
||||
|
@ -38,6 +38,8 @@ def upgrade():
|
||||
"""
|
||||
bind = op.get_bind()
|
||||
session = Session(bind=bind)
|
||||
|
||||
op.alter_column('user', 'realname', type_=sa.String(255), existing_type=sa.String(20))
|
||||
|
||||
#delete column access_log.user_id(access_log_ibfk_1), access_log.project_id(access_log_ibfk_2)
|
||||
op.drop_constraint('access_log_ibfk_1', 'access_log', type_='foreignkey')
|
||||
|
Loading…
Reference in New Issue
Block a user