Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Steven Zou 2017-07-07 00:39:29 +08:00
commit cadc1187c2
35 changed files with 839 additions and 671 deletions

View File

@ -92,7 +92,7 @@ REBUILDCLARITYFLAG=false
NEWCLARITYVERSION=
#clair parameters
CLAIRVERSION=v2.0.0
CLAIRVERSION=v2.0.1
CLAIRFLAG=false
CLAIRDBVERSION=9.6.3-photon

View File

@ -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,

View File

@ -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,
@ -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,

View File

@ -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

View File

@ -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
View 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
}

View File

@ -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)
}
}

View File

@ -28,5 +28,6 @@ func init() {
new(AccessLog),
new(ScanJob),
new(RepoRecord),
new(ImgScanOverview))
new(ImgScanOverview),
new(ClairVulnTimestamp))
}

View File

@ -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"`
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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])
}

View File

@ -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
}

View File

@ -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])
}

View File

@ -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
}

View File

@ -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))

View File

@ -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]
}

View File

@ -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)

View File

@ -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]
}

View File

@ -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")
}

View File

@ -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])
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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", &registry.NotificationHandler{})
beego.Router("/service/notifications/clair", &clair.Handler{}, "post:Handle")
beego.Router("/service/token", &token.Handler{})
beego.Router("/registryproxy/*", &controllers.RegistryProxy{}, "*:Handle")

View 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)
}
}

View File

@ -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"

View File

@ -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.

View File

@ -45,3 +45,4 @@ Changelog for harbor database schema
- 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`
- alter column `realname` on table `user`: varchar(20)->varchar(255)

View File

@ -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))

View File

@ -39,6 +39,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')
op.drop_constraint('access_log_ibfk_2', 'access_log', type_='foreignkey')