2017-01-13 07:08:52 +00:00
|
|
|
// Copyright 2017 clair authors
|
2016-05-17 21:22:18 +00:00
|
|
|
//
|
|
|
|
// 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.
|
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
package notification
|
2016-01-21 23:09:23 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2017-07-26 23:23:54 +00:00
|
|
|
"errors"
|
2016-01-21 23:09:23 +00:00
|
|
|
"time"
|
|
|
|
|
2016-01-26 22:57:32 +00:00
|
|
|
"github.com/guregu/null/zero"
|
2017-05-04 17:21:25 +00:00
|
|
|
|
|
|
|
"github.com/coreos/clair/database"
|
2019-03-06 21:35:35 +00:00
|
|
|
"github.com/coreos/clair/database/pgsql/util"
|
|
|
|
"github.com/coreos/clair/database/pgsql/vulnerability"
|
2017-05-04 17:21:25 +00:00
|
|
|
"github.com/coreos/clair/pkg/commonerr"
|
2018-09-07 20:12:19 +00:00
|
|
|
"github.com/coreos/clair/pkg/pagination"
|
2016-01-21 23:09:23 +00:00
|
|
|
)
|
|
|
|
|
2018-09-19 19:38:07 +00:00
|
|
|
const (
|
|
|
|
insertNotification = `
|
|
|
|
INSERT INTO Vulnerability_Notification(name, created_at, old_vulnerability_id, new_vulnerability_id)
|
|
|
|
VALUES ($1, $2, $3, $4)`
|
|
|
|
|
|
|
|
updatedNotificationAsRead = `
|
|
|
|
UPDATE Vulnerability_Notification
|
|
|
|
SET notified_at = CURRENT_TIMESTAMP
|
|
|
|
WHERE name = $1`
|
|
|
|
|
|
|
|
removeNotification = `
|
|
|
|
UPDATE Vulnerability_Notification
|
|
|
|
SET deleted_at = CURRENT_TIMESTAMP
|
|
|
|
WHERE name = $1 AND deleted_at IS NULL`
|
|
|
|
|
|
|
|
searchNotificationAvailable = `
|
|
|
|
SELECT name, created_at, notified_at, deleted_at
|
|
|
|
FROM Vulnerability_Notification
|
|
|
|
WHERE (notified_at IS NULL OR notified_at < $1)
|
|
|
|
AND deleted_at IS NULL
|
|
|
|
AND name NOT IN (SELECT name FROM Lock)
|
|
|
|
ORDER BY Random()
|
|
|
|
LIMIT 1`
|
|
|
|
|
|
|
|
searchNotification = `
|
|
|
|
SELECT created_at, notified_at, deleted_at, old_vulnerability_id, new_vulnerability_id
|
|
|
|
FROM Vulnerability_Notification
|
|
|
|
WHERE name = $1`
|
|
|
|
)
|
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
func queryInsertNotifications(count int) string {
|
|
|
|
return util.QueryInsert(count,
|
|
|
|
"vulnerability_notification",
|
|
|
|
"name",
|
|
|
|
"created_at",
|
|
|
|
"old_vulnerability_id",
|
|
|
|
"new_vulnerability_id",
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
var (
|
2019-03-06 21:35:35 +00:00
|
|
|
errNotificationNotFound = errors.New("requested notification is not found")
|
|
|
|
errVulnerabilityNotFound = errors.New("vulnerability is not in database")
|
2017-07-26 23:23:54 +00:00
|
|
|
)
|
2016-01-21 23:09:23 +00:00
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
func InsertVulnerabilityNotifications(tx *sql.Tx, notifications []database.VulnerabilityNotification) error {
|
2017-07-26 23:23:54 +00:00
|
|
|
if len(notifications) == 0 {
|
|
|
|
return nil
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
var (
|
|
|
|
newVulnIDMap = make(map[database.VulnerabilityID]sql.NullInt64)
|
|
|
|
oldVulnIDMap = make(map[database.VulnerabilityID]sql.NullInt64)
|
|
|
|
)
|
2016-01-21 23:09:23 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
invalidCreationTime := time.Time{}
|
|
|
|
for _, noti := range notifications {
|
|
|
|
if noti.Name == "" {
|
|
|
|
return commonerr.NewBadRequestError("notification should not have empty name")
|
|
|
|
}
|
|
|
|
if noti.Created == invalidCreationTime {
|
|
|
|
return commonerr.NewBadRequestError("notification should not have empty created time")
|
|
|
|
}
|
2016-01-24 03:02:34 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
if noti.New != nil {
|
|
|
|
key := database.VulnerabilityID{
|
|
|
|
Name: noti.New.Name,
|
|
|
|
Namespace: noti.New.Namespace.Name,
|
|
|
|
}
|
|
|
|
newVulnIDMap[key] = sql.NullInt64{}
|
|
|
|
}
|
2016-01-22 20:59:46 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
if noti.Old != nil {
|
|
|
|
key := database.VulnerabilityID{
|
|
|
|
Name: noti.Old.Name,
|
|
|
|
Namespace: noti.Old.Namespace.Name,
|
|
|
|
}
|
|
|
|
oldVulnIDMap[key] = sql.NullInt64{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
newVulnIDs = make([]database.VulnerabilityID, 0, len(newVulnIDMap))
|
|
|
|
oldVulnIDs = make([]database.VulnerabilityID, 0, len(oldVulnIDMap))
|
|
|
|
)
|
2016-01-21 23:09:23 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
for vulnID := range newVulnIDMap {
|
|
|
|
newVulnIDs = append(newVulnIDs, vulnID)
|
|
|
|
}
|
2016-01-24 03:02:34 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
for vulnID := range oldVulnIDMap {
|
|
|
|
oldVulnIDs = append(oldVulnIDs, vulnID)
|
|
|
|
}
|
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
ids, err := vulnerability.FindNotDeletedVulnerabilityIDs(tx, newVulnIDs)
|
2016-01-26 22:57:32 +00:00
|
|
|
if err != nil {
|
2017-07-26 23:23:54 +00:00
|
|
|
return err
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
for i, id := range ids {
|
|
|
|
if !id.Valid {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("findNotDeletedVulnerabilityIDs", errVulnerabilityNotFound)
|
2017-07-26 23:23:54 +00:00
|
|
|
}
|
|
|
|
newVulnIDMap[newVulnIDs[i]] = id
|
|
|
|
}
|
2016-02-04 16:34:01 +00:00
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
ids, err = vulnerability.FindLatestDeletedVulnerabilityIDs(tx, oldVulnIDs)
|
2016-01-26 22:57:32 +00:00
|
|
|
if err != nil {
|
2017-07-26 23:23:54 +00:00
|
|
|
return err
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
for i, id := range ids {
|
|
|
|
if !id.Valid {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("findLatestDeletedVulnerabilityIDs", errVulnerabilityNotFound)
|
2017-07-26 23:23:54 +00:00
|
|
|
}
|
|
|
|
oldVulnIDMap[oldVulnIDs[i]] = id
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
newVulnID sql.NullInt64
|
|
|
|
oldVulnID sql.NullInt64
|
2016-02-04 16:34:01 +00:00
|
|
|
)
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
keys := make([]interface{}, len(notifications)*4)
|
|
|
|
for i, noti := range notifications {
|
|
|
|
if noti.New != nil {
|
|
|
|
newVulnID = newVulnIDMap[database.VulnerabilityID{
|
|
|
|
Name: noti.New.Name,
|
|
|
|
Namespace: noti.New.Namespace.Name,
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
|
|
|
|
if noti.Old != nil {
|
|
|
|
oldVulnID = oldVulnIDMap[database.VulnerabilityID{
|
|
|
|
Name: noti.Old.Name,
|
|
|
|
Namespace: noti.Old.Namespace.Name,
|
|
|
|
}]
|
|
|
|
}
|
|
|
|
|
|
|
|
keys[4*i] = noti.Name
|
|
|
|
keys[4*i+1] = noti.Created
|
|
|
|
keys[4*i+2] = oldVulnID
|
|
|
|
keys[4*i+3] = newVulnID
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE(Sida): The data is not sorted before inserting into database under
|
|
|
|
// the fact that there's only one updater running at a time. If there are
|
|
|
|
// multiple updaters, deadlock may happen.
|
|
|
|
_, err = tx.Exec(queryInsertNotifications(len(notifications)), keys...)
|
2016-01-26 22:57:32 +00:00
|
|
|
if err != nil {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("queryInsertNotifications", err)
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
return nil
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
func FindNewNotification(tx *sql.Tx, notifiedBefore time.Time) (database.NotificationHook, bool, error) {
|
2017-07-26 23:23:54 +00:00
|
|
|
var (
|
|
|
|
notification database.NotificationHook
|
|
|
|
created zero.Time
|
|
|
|
notified zero.Time
|
|
|
|
deleted zero.Time
|
|
|
|
)
|
2016-02-04 16:34:01 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
err := tx.QueryRow(searchNotificationAvailable, notifiedBefore).Scan(¬ification.Name, &created, ¬ified, &deleted)
|
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return notification, false, nil
|
2016-02-04 16:34:01 +00:00
|
|
|
}
|
2019-03-06 21:35:35 +00:00
|
|
|
return notification, false, util.HandleError("searchNotificationAvailable", err)
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
|
|
|
|
2016-01-26 22:57:32 +00:00
|
|
|
notification.Created = created.Time
|
|
|
|
notification.Notified = notified.Time
|
|
|
|
notification.Deleted = deleted.Time
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
return notification, true, nil
|
|
|
|
}
|
2016-02-04 16:34:01 +00:00
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
func FindVulnerabilityNotification(tx *sql.Tx, name string, limit int, oldPageToken pagination.Token, newPageToken pagination.Token, key pagination.Key) (
|
2017-07-26 23:23:54 +00:00
|
|
|
database.VulnerabilityNotificationWithVulnerable, bool, error) {
|
|
|
|
var (
|
|
|
|
noti database.VulnerabilityNotificationWithVulnerable
|
|
|
|
oldVulnID sql.NullInt64
|
|
|
|
newVulnID sql.NullInt64
|
|
|
|
created zero.Time
|
|
|
|
notified zero.Time
|
|
|
|
deleted zero.Time
|
|
|
|
)
|
2016-01-26 22:57:32 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
if name == "" {
|
|
|
|
return noti, false, commonerr.NewBadRequestError("Empty notification name is not allowed")
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
2016-01-26 22:57:32 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
noti.Name = name
|
|
|
|
err := tx.QueryRow(searchNotification, name).Scan(&created, ¬ified,
|
|
|
|
&deleted, &oldVulnID, &newVulnID)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
return noti, false, nil
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
2019-03-06 21:35:35 +00:00
|
|
|
return noti, false, util.HandleError("searchNotification", err)
|
2017-07-26 23:23:54 +00:00
|
|
|
}
|
2016-01-21 23:09:23 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
if created.Valid {
|
|
|
|
noti.Created = created.Time
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
2017-07-26 23:23:54 +00:00
|
|
|
|
|
|
|
if notified.Valid {
|
|
|
|
noti.Notified = notified.Time
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
if deleted.Valid {
|
|
|
|
noti.Deleted = deleted.Time
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
if oldVulnID.Valid {
|
2019-03-06 21:35:35 +00:00
|
|
|
page, err := vulnerability.FindPagedVulnerableAncestries(tx, oldVulnID.Int64, limit, oldPageToken, key)
|
2017-07-26 23:23:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return noti, false, err
|
|
|
|
}
|
|
|
|
noti.Old = &page
|
2016-01-26 22:57:32 +00:00
|
|
|
}
|
2016-01-21 23:09:23 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
if newVulnID.Valid {
|
2019-03-06 21:35:35 +00:00
|
|
|
page, err := vulnerability.FindPagedVulnerableAncestries(tx, newVulnID.Int64, limit, newPageToken, key)
|
2017-07-26 23:23:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return noti, false, err
|
|
|
|
}
|
|
|
|
noti.New = &page
|
|
|
|
}
|
|
|
|
|
|
|
|
return noti, true, nil
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
func MarkNotificationAsRead(tx *sql.Tx, name string) error {
|
2017-07-26 23:23:54 +00:00
|
|
|
if name == "" {
|
|
|
|
return commonerr.NewBadRequestError("Empty notification name is not allowed")
|
|
|
|
}
|
2016-01-24 03:02:34 +00:00
|
|
|
|
2018-09-11 18:24:09 +00:00
|
|
|
r, err := tx.Exec(updatedNotificationAsRead, name)
|
2017-07-26 23:23:54 +00:00
|
|
|
if err != nil {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("updatedNotificationAsRead", err)
|
2017-07-26 23:23:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
affected, err := r.RowsAffected()
|
|
|
|
if err != nil {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("updatedNotificationAsRead", err)
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
2017-07-26 23:23:54 +00:00
|
|
|
|
|
|
|
if affected <= 0 {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("updatedNotificationAsRead", errNotificationNotFound)
|
2017-07-26 23:23:54 +00:00
|
|
|
}
|
2016-01-21 23:09:23 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-06 21:35:35 +00:00
|
|
|
func DeleteNotification(tx *sql.Tx, name string) error {
|
2017-07-26 23:23:54 +00:00
|
|
|
if name == "" {
|
|
|
|
return commonerr.NewBadRequestError("Empty notification name is not allowed")
|
|
|
|
}
|
2016-01-24 03:02:34 +00:00
|
|
|
|
2017-07-26 23:23:54 +00:00
|
|
|
result, err := tx.Exec(removeNotification, name)
|
2016-01-21 23:09:23 +00:00
|
|
|
if err != nil {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("removeNotification", err)
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
affected, err := result.RowsAffected()
|
|
|
|
if err != nil {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("removeNotification", err)
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if affected <= 0 {
|
2019-03-06 21:35:35 +00:00
|
|
|
return util.HandleError("removeNotification", commonerr.ErrNotFound)
|
2016-01-21 23:09:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|