From 18cea61121a2ffe645dd88a4d74f577b18a3e01a Mon Sep 17 00:00:00 2001 From: "Archambault, Samuel" Date: Mon, 19 Jun 2017 12:19:30 -0400 Subject: [PATCH 1/8] Allow 255 chars for Realname --- make/common/db/registry.sql | 2 +- make/common/db/registry_sqlite.sql | 2 +- src/ui/api/user.go | 2 +- tools/migration/db_meta.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/make/common/db/registry.sql b/make/common/db/registry.sql index 2c4a8ab04..7b7440c12 100644 --- a/make/common/db/registry.sql +++ b/make/common/db/registry.sql @@ -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, diff --git a/make/common/db/registry_sqlite.sql b/make/common/db/registry_sqlite.sql index fecc00610..d84a4aeb5 100644 --- a/make/common/db/registry_sqlite.sql +++ b/make/common/db/registry_sqlite.sql @@ -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, diff --git a/src/ui/api/user.go b/src/ui/api/user.go index 7ad80f4eb..19ff9b612 100644 --- a/src/ui/api/user.go +++ b/src/ui/api/user.go @@ -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") } diff --git a/tools/migration/db_meta.py b/tools/migration/db_meta.py index c928edc17..c5655ec76 100644 --- a/tools/migration/db_meta.py +++ b/tools/migration/db_meta.py @@ -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)) From 99a492270e1751355c3ae20699083a909a0aff99 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Fri, 30 Jun 2017 13:40:25 +0800 Subject: [PATCH 2/8] just return tag list rather than 500 error if there are errors --- src/ui/api/repository.go | 116 +++++++++++++++------------------------ 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 3a23f3c9a..98862fe5e 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -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,92 @@ 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 + 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) } + 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 } From 34c812243ebe42dddf070bd777de01529438982b Mon Sep 17 00:00:00 2001 From: yixingj Date: Tue, 4 Jul 2017 17:44:10 +0800 Subject: [PATCH 3/8] Update Clair to 2.0.1 fix the Debian mapping issue. --- make/docker-compose.clair.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/docker-compose.clair.yml b/make/docker-compose.clair.yml index 3634cbe24..48d98de37 100644 --- a/make/docker-compose.clair.yml +++ b/make/docker-compose.clair.yml @@ -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 From 8b31715b3469fc0e21af567407136b1c0c3a6b67 Mon Sep 17 00:00:00 2001 From: Tan Jiang Date: Tue, 4 Jul 2017 12:12:16 +0800 Subject: [PATCH 4/8] provide Clair notification handler update the timestamp in DB, when handling the notification --- make/common/db/registry.sql | 8 ++ make/common/db/registry_sqlite.sql | 9 +- make/common/templates/clair/config.yaml | 4 +- src/common/dao/clair.go | 54 +++++++++ src/common/dao/dao_test.go | 30 +++++ src/common/models/base.go | 3 +- src/common/models/clair.go | 50 ++++++++ src/common/utils/clair/client.go | 52 +++++++++ src/ui/router.go | 6 +- src/ui/service/notifications/clair/handler.go | 109 ++++++++++++++++++ .../registry/handler.go} | 2 +- 11 files changed, 321 insertions(+), 6 deletions(-) create mode 100644 src/common/dao/clair.go create mode 100644 src/ui/service/notifications/clair/handler.go rename src/ui/service/{notification.go => notifications/registry/handler.go} (99%) diff --git a/make/common/db/registry.sql b/make/common/db/registry.sql index 97ad883b2..4f29491db 100644 --- a/make/common/db/registry.sql +++ b/make/common/db/registry.sql @@ -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, diff --git a/make/common/db/registry_sqlite.sql b/make/common/db/registry_sqlite.sql index fecc00610..e6432d44a 100644 --- a/make/common/db/registry_sqlite.sql +++ b/make/common/db/registry_sqlite.sql @@ -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, diff --git a/make/common/templates/clair/config.yaml b/make/common/templates/clair/config.yaml index f93129e4a..e8f70ab35 100644 --- a/make/common/templates/clair/config.yaml +++ b/make/common/templates/clair/config.yaml @@ -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 diff --git a/src/common/dao/clair.go b/src/common/dao/clair.go new file mode 100644 index 000000000..e2343b11f --- /dev/null +++ b/src/common/dao/clair.go @@ -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 +} diff --git a/src/common/dao/dao_test.go b/src/common/dao/dao_test.go index a2a155031..fd04ae814 100644 --- a/src/common/dao/dao_test.go +++ b/src/common/dao/dao_test.go @@ -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) + } +} diff --git a/src/common/models/base.go b/src/common/models/base.go index 7fec6cb70..f3cbfb935 100644 --- a/src/common/models/base.go +++ b/src/common/models/base.go @@ -28,5 +28,6 @@ func init() { new(AccessLog), new(ScanJob), new(RepoRecord), - new(ImgScanOverview)) + new(ImgScanOverview), + new(ClairVulnTimestamp)) } diff --git a/src/common/models/clair.go b/src/common/models/clair.go index 13f123ad5..da433cf86 100644 --- a/src/common/models/clair.go +++ b/src/common/models/clair.go @@ -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"` +} diff --git a/src/common/utils/clair/client.go b/src/common/utils/clair/client.go index dc2b73dc7..40bbef1d0 100644 --- a/src/common/utils/clair/client.go +++ b/src/common/utils/clair/client.go @@ -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 +} diff --git a/src/ui/router.go b/src/ui/router.go index 120d3d419..b3a7b66b2 100644 --- a/src/ui/router.go +++ b/src/ui/router.go @@ -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") diff --git a/src/ui/service/notifications/clair/handler.go b/src/ui/service/notifications/clair/handler.go new file mode 100644 index 000000000..850fbd817 --- /dev/null +++ b/src/ui/service/notifications/clair/handler.go @@ -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) + } +} diff --git a/src/ui/service/notification.go b/src/ui/service/notifications/registry/handler.go similarity index 99% rename from src/ui/service/notification.go rename to src/ui/service/notifications/registry/handler.go index 8a08b51e6..20092f647 100644 --- a/src/ui/service/notification.go +++ b/src/ui/service/notifications/registry/handler.go @@ -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" From b866200e4fc43cd069d3041c868e40ff5e7bf4b2 Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Wed, 5 Jul 2017 16:06:58 +0800 Subject: [PATCH 5/8] Move some method of project manager to security context --- src/common/security/admiral/context.go | 67 ++-- .../security/authcontext/authcontext.go | 100 +++--- .../security/authcontext/authcontext_test.go | 59 ++- src/common/security/context.go | 6 + src/common/security/local/context.go | 129 ++++--- src/common/security/local/context_test.go | 336 ++++++++---------- src/common/security/secret/context.go | 18 + src/common/security/secret/context_test.go | 32 ++ src/ui/api/log.go | 2 +- src/ui/api/project.go | 8 +- src/ui/api/repository.go | 2 +- src/ui/api/search.go | 48 ++- src/ui/projectmanager/db/pm.go | 67 ---- src/ui/projectmanager/db/pm_test.go | 88 ----- src/ui/projectmanager/pm.go | 7 - src/ui/projectmanager/pms/pm.go | 29 +- src/ui/projectmanager/pms/pm_test.go | 38 -- src/ui/service/token/token_test.go | 7 + 18 files changed, 458 insertions(+), 585 deletions(-) diff --git a/src/common/security/admiral/context.go b/src/common/security/admiral/context.go index 295e4ce81..a848ceae1 100644 --- a/src/common/security/admiral/context.go +++ b/src/common/security/admiral/context.go @@ -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) +} diff --git a/src/common/security/authcontext/authcontext.go b/src/common/security/authcontext/authcontext.go index 370ae73fe..927be96d0 100644 --- a/src/common/security/authcontext/authcontext.go +++ b/src/common/security/authcontext/authcontext.go @@ -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) diff --git a/src/common/security/authcontext/authcontext_test.go b/src/common/security/authcontext/authcontext_test.go index 609062c6b..a18169c5c 100644 --- a/src/common/security/authcontext/authcontext_test.go +++ b/src/common/security/authcontext/authcontext_test.go @@ -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)) +} diff --git a/src/common/security/context.go b/src/common/security/context.go index e491285a1..312db2438 100644 --- a/src/common/security/context.go +++ b/src/common/security/context.go @@ -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 } diff --git a/src/common/security/local/context.go b/src/common/security/local/context.go index 440bc3bcc..fc923d4ee 100644 --- a/src/common/security/local/context.go +++ b/src/common/security/local/context.go @@ -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 +} diff --git a/src/common/security/local/context_test.go b/src/common/security/local/context_test.go index 95fe7c7aa..0bee8da22 100644 --- a/src/common/security/local/context_test.go +++ b/src/common/security/local/context_test.go @@ -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]) } diff --git a/src/common/security/secret/context.go b/src/common/security/secret/context.go index f8e8a30c4..328a134ba 100644 --- a/src/common/security/secret/context.go +++ b/src/common/security/secret/context.go @@ -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 +} diff --git a/src/common/security/secret/context_test.go b/src/common/security/secret/context_test.go index 45db03182..8d221c14b 100644 --- a/src/common/security/secret/context_test.go +++ b/src/common/security/secret/context_test.go @@ -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]) +} diff --git a/src/ui/api/log.go b/src/ui/api/log.go index d9fdb6640..5e3833f0b 100644 --- a/src/ui/api/log.go +++ b/src/ui/api/log.go @@ -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)) diff --git a/src/ui/api/project.go b/src/ui/api/project.go index 1743b1097..55c4298f5 100644 --- a/src/ui/api/project.go +++ b/src/ui/api/project.go @@ -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] } diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 9f2283348..2da5f9ec1 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -608,7 +608,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)) diff --git a/src/ui/api/search.go b/src/ui/api/search.go index 24b60bd7a..5f90c0823 100644 --- a/src/ui/api/search.go +++ b/src/ui/api/search.go @@ -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] } diff --git a/src/ui/projectmanager/db/pm.go b/src/ui/projectmanager/db/pm.go index fdcc7561d..4f659d6a1 100644 --- a/src/ui/projectmanager/db/pm.go +++ b/src/ui/projectmanager/db/pm.go @@ -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]) -} diff --git a/src/ui/projectmanager/db/pm_test.go b/src/ui/projectmanager/db/pm_test.go index b705132c9..227ed6063 100644 --- a/src/ui/projectmanager/db/pm_test.go +++ b/src/ui/projectmanager/db/pm_test.go @@ -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) -} diff --git a/src/ui/projectmanager/pm.go b/src/ui/projectmanager/pm.go index 676dfe9c3..4a66966e1 100644 --- a/src/ui/projectmanager/pm.go +++ b/src/ui/projectmanager/pm.go @@ -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) } diff --git a/src/ui/projectmanager/pms/pm.go b/src/ui/projectmanager/pms/pm.go index 39517033b..571f81789 100644 --- a/src/ui/projectmanager/pms/pm.go +++ b/src/ui/projectmanager/pms/pm.go @@ -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 { diff --git a/src/ui/projectmanager/pms/pm_test.go b/src/ui/projectmanager/pms/pm_test.go index aa14d6fde..bece67d0f 100644 --- a/src/ui/projectmanager/pms/pm_test.go +++ b/src/ui/projectmanager/pms/pm_test.go @@ -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 { diff --git a/src/ui/service/token/token_test.go b/src/ui/service/token/token_test.go index a6a6207bc..a5828971e 100644 --- a/src/ui/service/token/token_test.go +++ b/src/ui/service/token/token_test.go @@ -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. From b2ac6c37d22db050c0e0241d3c147de0839b67c1 Mon Sep 17 00:00:00 2001 From: yixingj Date: Wed, 5 Jul 2017 18:28:41 +0800 Subject: [PATCH 6/8] Also update clair in offline package --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a607eca4a..f7907b54e 100644 --- a/Makefile +++ b/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 From bdf4641c43956c981d3db9cec40d3f96a71b1bf9 Mon Sep 17 00:00:00 2001 From: wangyan Date: Wed, 5 Jul 2017 03:34:40 -0700 Subject: [PATCH 7/8] update user realname update --- tools/migration/changelog.md | 3 ++- tools/migration/migration_harbor/versions/1_2_0.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/migration/changelog.md b/tools/migration/changelog.md index ac279b560..20ea6d8e2 100644 --- a/tools/migration/changelog.md +++ b/tools/migration/changelog.md @@ -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` \ No newline at end of file + - add column `username` varchar (32) to table `access_log` + - alter column `realname` on table `user`: varchar(20)->varchar(255) \ No newline at end of file diff --git a/tools/migration/migration_harbor/versions/1_2_0.py b/tools/migration/migration_harbor/versions/1_2_0.py index 05c712f1d..5c41509fd 100644 --- a/tools/migration/migration_harbor/versions/1_2_0.py +++ b/tools/migration/migration_harbor/versions/1_2_0.py @@ -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') From 7b39a46c9822dc1323a1b1171e3bbf5008b34c7a Mon Sep 17 00:00:00 2001 From: Wenkai Yin Date: Thu, 6 Jul 2017 14:48:43 +0800 Subject: [PATCH 8/8] update --- src/ui/api/repository.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ui/api/repository.go b/src/ui/api/repository.go index 98862fe5e..7a300d729 100644 --- a/src/ui/api/repository.go +++ b/src/ui/api/repository.go @@ -380,6 +380,7 @@ func assemble(client *registry.Repository, repository string, if config.WithNotary() { signatures, err = getSignatures(repository, username) if err != nil { + signatures = map[string]*notary.Target{} log.Errorf("failed to get signatures of %s: %v", repository, err) } } @@ -397,7 +398,9 @@ func assemble(client *registry.Repository, repository string, } log.Errorf("failed to get v2 manifest of %s:%s: %v", repository, t, err) } - item.tag = *cfg + if cfg != nil { + item.tag = *cfg + } // scan overview if config.WithClair() { @@ -796,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)