From a541e964e07ea0e9a70f2ebee68897edf852bcba Mon Sep 17 00:00:00 2001 From: liangchenye Date: Fri, 26 Feb 2016 18:18:45 +0800 Subject: [PATCH 1/4] New API: list vulnerabilities by namespace Signed-off-by: liangchenye --- api/v1/README.md | 55 +++++++++++++++++++++++++++++++++ api/v1/models.go | 5 +-- api/v1/router.go | 1 + api/v1/routes.go | 48 ++++++++++++++++++++++++++++ database/database.go | 4 +++ database/pgsql/queries.go | 3 ++ database/pgsql/vulnerability.go | 44 ++++++++++++++++++++++++++ 7 files changed, 158 insertions(+), 2 deletions(-) diff --git a/api/v1/README.md b/api/v1/README.md index e71f8aeca5..f11929bf5f 100644 --- a/api/v1/README.md +++ b/api/v1/README.md @@ -8,6 +8,7 @@ - [Namespaces](#namespaces) - [GET](#get-namespaces) - [Vulnerabilities](#vulnerabilities) + - [List](#get-namespacesnsnamevulnerabilities) - [POST](#post-namespacesnamevulnerabilities) - [GET](#get-namespacesnsnamevulnerabilitiesvulnname) - [PUT](#put-namespacesnsnamevulnerabilitiesvulnname) @@ -196,6 +197,60 @@ Server: clair ## Vulnerabilities +#### GET /namespaces/`:nsName`/vulnerabilities + +###### Description + +The GET route for the Vulnerabilities resource displays the vulnerabilities data for a given namespace. + +###### Query Parameters + +| Name | Type | Required | Description | +|---------|------|----------|------------------------------------------------------------| +| limit | int | required | Limits the amount of the vunlerabilities data for a given namespace. | +| page | int | required | Displays the specific page of the vunlerabilities data for a given namespace. | + +###### Example Request + +```json +GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities?page=0&limit=2 HTTP/1.1 +``` + +###### Example Response + +```json +HTTP/1.1 200 OK +Content-Type: application/json;charset=utf-8 +Server: clair + +{ + "Vulnerabilities": [ + { + "Name": "CVE-1999-1332", + "Namespace": "debian:8", + "Description": "gzexe in the gzip package on Red Hat Linux 5.0 and earlier allows local users to overwrite files of other users via a symlink attack on a temporary file.", + "Link": "https://security-tracker.debian.org/tracker/CVE-1999-1332", + "Severity": "Low" + }, + { + "Name": "CVE-1999-1572", + "Namespace": "debian:8", + "Description": "cpio on FreeBSD 2.1.0, Debian GNU/Linux 3.0, and possibly other operating systems, uses a 0 umask when creating files using the -O (archive) or -F options, which creates the files with mode 0666 and allows local users to read or overwrite those files.", + "Link": "https://security-tracker.debian.org/tracker/CVE-1999-1572", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 2.1, + "Vectors": "AV:L/AC:L/Au:N/C:P/I:N" + } + } + } + } + ] +} +``` + #### POST /namespaces/`:name`/vulnerabilities ###### Description diff --git a/api/v1/models.go b/api/v1/models.go index d96dd58987..456024e51f 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -274,8 +274,9 @@ type NamespaceEnvelope struct { } type VulnerabilityEnvelope struct { - Vulnerability *Vulnerability `json:"Vulnerability,omitempty"` - Error *Error `json:"Error,omitempty"` + Vulnerability *Vulnerability `json:"Vulnerability,omitempty"` + Vulnerabilities *[]Vulnerability `json:"Vulnerabilities,omitempty"` + Error *Error `json:"Error,omitempty"` } type NotificationEnvelope struct { diff --git a/api/v1/router.go b/api/v1/router.go index c4c6662ad9..5a3d640ea6 100644 --- a/api/v1/router.go +++ b/api/v1/router.go @@ -34,6 +34,7 @@ func NewRouter(ctx *context.RouteContext) *httprouter.Router { router.GET("/namespaces", context.HTTPHandler(getNamespaces, ctx)) // Vulnerabilities + router.GET("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(getVulnerabilities, ctx)) router.POST("/namespaces/:namespaceName/vulnerabilities", context.HTTPHandler(postVulnerability, ctx)) router.GET("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(getVulnerability, ctx)) router.PUT("/namespaces/:namespaceName/vulnerabilities/:vulnerabilityName", context.HTTPHandler(putVulnerability, ctx)) diff --git a/api/v1/routes.go b/api/v1/routes.go index f380ac5c52..f7357f2a04 100644 --- a/api/v1/routes.go +++ b/api/v1/routes.go @@ -38,6 +38,7 @@ const ( getLayerRoute = "v1/getLayer" deleteLayerRoute = "v1/deleteLayer" getNamespacesRoute = "v1/getNamespaces" + getVulnerabilitiesRoute = "v1/getVulnerabilities" postVulnerabilityRoute = "v1/postVulnerability" getVulnerabilityRoute = "v1/getVulnerability" putVulnerabilityRoute = "v1/putVulnerability" @@ -184,6 +185,53 @@ func getNamespaces(w http.ResponseWriter, r *http.Request, p httprouter.Params, return getNamespacesRoute, http.StatusOK } +func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { + query := r.URL.Query() + + limitStrs, limitExists := query["limit"] + if !limitExists { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"must provide limit query parameter"}}) + return getVulnerabilitiesRoute, http.StatusBadRequest + } + limit, err := strconv.Atoi(limitStrs[0]) + if err != nil { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid limit format: " + err.Error()}}) + return getVulnerabilitiesRoute, http.StatusBadRequest + } else if limit <= 0 { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"limit value should not be less than zero"}}) + return getVulnerabilitiesRoute, http.StatusBadRequest + } + + pageStrs, pageExists := query["page"] + if !pageExists { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"must provide page query parameter"}}) + return getVulnerabilitiesRoute, http.StatusBadRequest + } + page, err := strconv.Atoi(pageStrs[0]) + if err != nil { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) + return getVulnerabilitiesRoute, http.StatusBadRequest + } else if page < 0 { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"page value should not be less than zero"}}) + return getVulnerabilitiesRoute, http.StatusBadRequest + } + + dbVulns, err := ctx.Store.ListVulnerabilities(p.ByName("namespaceName"), limit, page) + if err != nil { + writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}}) + return getVulnerabilitiesRoute, http.StatusInternalServerError + } + + var vulns []Vulnerability + for _, dbVuln := range dbVulns { + vuln := VulnerabilityFromDatabaseModel(dbVuln, false) + vulns = append(vulns, vuln) + } + + writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns}) + return getVulnerabilitiesRoute, http.StatusOK +} + func postVulnerability(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *context.RouteContext) (string, int) { request := VulnerabilityEnvelope{} err := decodeJSON(r, &request) diff --git a/database/database.go b/database/database.go index cf30cb82cf..cec45f0f80 100644 --- a/database/database.go +++ b/database/database.go @@ -60,6 +60,10 @@ type Datastore interface { DeleteLayer(name string) error // # Vulnerability + // ListVulnerabilities returns the list of vulnerabilies of a certain Namespace. + // The Limit and page parameters are used to paginate the return list. + ListVulnerabilities(namespaceName string, limit int, page int) ([]Vulnerability, error) + // InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if // necessary. A vulnerability is uniquely identified by its Namespace and its Name. // The FixedIn field may only contain a partial list of Features that are affected by the diff --git a/database/pgsql/queries.go b/database/pgsql/queries.go index 22ca33b8aa..a79f4a86c2 100644 --- a/database/pgsql/queries.go +++ b/database/pgsql/queries.go @@ -144,6 +144,9 @@ const ( searchVulnerabilityForUpdate = ` FOR UPDATE OF v` searchVulnerabilityByNamespaceAndName = ` WHERE n.name = $1 AND v.name = $2 AND v.deleted_at IS NULL` searchVulnerabilityByID = ` WHERE v.id = $1` + searchVulnerabilityByNamespace = ` WHERE n.name = $1 AND v.deleted_at IS NULL + ORDER BY v.name + LIMIT $2 offset $3` searchVulnerabilityFixedIn = ` SELECT vfif.version, f.id, f.Name diff --git a/database/pgsql/vulnerability.go b/database/pgsql/vulnerability.go index ca53801a5e..ade3a6f65d 100644 --- a/database/pgsql/vulnerability.go +++ b/database/pgsql/vulnerability.go @@ -28,6 +28,50 @@ import ( "github.com/guregu/null/zero" ) +func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, page int) ([]database.Vulnerability, error) { + return listVulnerabilities(pgSQL, namespaceName, limit, page) +} + +func listVulnerabilities(queryer Queryer, namespaceName string, limit int, page int) ([]database.Vulnerability, error) { + defer observeQueryTime("listVulnerabilities", "all", time.Now()) + + // Query. + query := searchVulnerabilityBase + searchVulnerabilityByNamespace + rows, err := queryer.Query(query, namespaceName, limit, page*limit) + if err != nil { + return nil, handleError("searchVulnerabilityByNamespace", err) + } + defer rows.Close() + + var vulns []database.Vulnerability + // Scan query. + for rows.Next() { + var vulnerability database.Vulnerability + + err := rows.Scan( + &vulnerability.ID, + &vulnerability.Name, + &vulnerability.Namespace.ID, + &vulnerability.Namespace.Name, + &vulnerability.Description, + &vulnerability.Link, + &vulnerability.Severity, + &vulnerability.Metadata, + ) + if err != nil { + return nil, handleError("searchVulnerabilityByNamespace.Scan()", err) + } else { + vulns = append(vulns, vulnerability) + } + } + + if err := rows.Err(); err != nil { + return nil, handleError("searchVulnerabilityByNamespace.Rows()", err) + } + + return vulns, nil +} + func (pgSQL *pgSQL) FindVulnerability(namespaceName, name string) (database.Vulnerability, error) { return findVulnerability(pgSQL, namespaceName, name, false) } From 48ffb2687a2aaef9f8e0a29f779a852b48660c59 Mon Sep 17 00:00:00 2001 From: liangchenye Date: Mon, 29 Feb 2016 16:29:40 +0800 Subject: [PATCH 2/4] use encrypt page in listVuln api Signed-off-by: liangchenye --- api/v1/README.md | 5 +++-- api/v1/models.go | 23 +++++++++++++++++++++++ api/v1/routes.go | 27 ++++++++++++++------------- database/database.go | 4 +++- database/pgsql/queries.go | 5 +++-- database/pgsql/vulnerability.go | 23 +++++++++++++---------- 6 files changed, 59 insertions(+), 28 deletions(-) diff --git a/api/v1/README.md b/api/v1/README.md index f11929bf5f..3370b18512 100644 --- a/api/v1/README.md +++ b/api/v1/README.md @@ -213,7 +213,7 @@ The GET route for the Vulnerabilities resource displays the vulnerabilities data ###### Example Request ```json -GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities?page=0&limit=2 HTTP/1.1 +GET http://localhost:6060/v1/namespaces/debian%3A8/vulnerabilities?limit=2 HTTP/1.1 ``` ###### Example Response @@ -247,7 +247,8 @@ Server: clair } } } - ] + ], + "NextPage":"gAAAAABW1ABiOlm6KMDKYFE022bEy_IFJdm4ExxTNuJZMN0Eycn0Sut2tOH9bDB4EWGy5s6xwATUHiG-6JXXaU5U32sBs6_DmA==" } ``` diff --git a/api/v1/models.go b/api/v1/models.go index 456024e51f..7288528331 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -19,6 +19,7 @@ import ( "encoding/json" "errors" "fmt" + "strconv" "time" "github.com/coreos/clair/database" @@ -276,6 +277,7 @@ type NamespaceEnvelope struct { type VulnerabilityEnvelope struct { Vulnerability *Vulnerability `json:"Vulnerability,omitempty"` Vulnerabilities *[]Vulnerability `json:"Vulnerabilities,omitempty"` + NextPage *string `json:"NextPage,omitempty"` Error *Error `json:"Error,omitempty"` } @@ -317,3 +319,24 @@ func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key st return string(tokenBytes) } + +func tokenToNumber(token, key string) (int, error) { + k, _ := fernet.DecodeKey(key) + msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k}) + if msg == nil { + return -1, errors.New("invalid or expired pagination token") + } + + page, err := strconv.Atoi(string(msg)) + return page, err +} + +func numberToToken(page int, key string) string { + k, _ := fernet.DecodeKey(key) + tokenBytes, err := fernet.EncryptAndSign([]byte(strconv.Itoa(page)), k) + if err != nil { + log.Fatal("failed to encrypt number") + } + + return string(tokenBytes) +} diff --git a/api/v1/routes.go b/api/v1/routes.go index f7357f2a04..0dc48572ec 100644 --- a/api/v1/routes.go +++ b/api/v1/routes.go @@ -202,21 +202,17 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par return getVulnerabilitiesRoute, http.StatusBadRequest } + page := 0 pageStrs, pageExists := query["page"] - if !pageExists { - writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"must provide page query parameter"}}) - return getVulnerabilitiesRoute, http.StatusBadRequest - } - page, err := strconv.Atoi(pageStrs[0]) - if err != nil { - writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) - return getVulnerabilitiesRoute, http.StatusBadRequest - } else if page < 0 { - writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"page value should not be less than zero"}}) - return getVulnerabilitiesRoute, http.StatusBadRequest + if pageExists { + page, err = tokenToNumber(pageStrs[0], ctx.Config.PaginationKey) + if err != nil { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) + return getNotificationRoute, http.StatusBadRequest + } } - dbVulns, err := ctx.Store.ListVulnerabilities(p.ByName("namespaceName"), limit, page) + dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(p.ByName("namespaceName"), limit, page) if err != nil { writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}}) return getVulnerabilitiesRoute, http.StatusInternalServerError @@ -228,7 +224,12 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par vulns = append(vulns, vuln) } - writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns}) + var nextPageStr string + if nextPage != -1 { + nextPageStr = numberToToken(nextPage, ctx.Config.PaginationKey) + } + + writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns, NextPage: &nextPageStr}) return getVulnerabilitiesRoute, http.StatusOK } diff --git a/database/database.go b/database/database.go index cec45f0f80..0c84f99eca 100644 --- a/database/database.go +++ b/database/database.go @@ -62,7 +62,9 @@ type Datastore interface { // # Vulnerability // ListVulnerabilities returns the list of vulnerabilies of a certain Namespace. // The Limit and page parameters are used to paginate the return list. - ListVulnerabilities(namespaceName string, limit int, page int) ([]Vulnerability, error) + // The first given page should be 0. The function will then return the next available page. + // If there is no more page, -1 has to be returned. + ListVulnerabilities(namespaceName string, limit int, page int) ([]Vulnerability, int, error) // InsertVulnerabilities stores the given Vulnerabilities in the database, updating them if // necessary. A vulnerability is uniquely identified by its Namespace and its Name. diff --git a/database/pgsql/queries.go b/database/pgsql/queries.go index a79f4a86c2..0b67ecf53e 100644 --- a/database/pgsql/queries.go +++ b/database/pgsql/queries.go @@ -145,8 +145,9 @@ const ( searchVulnerabilityByNamespaceAndName = ` WHERE n.name = $1 AND v.name = $2 AND v.deleted_at IS NULL` searchVulnerabilityByID = ` WHERE v.id = $1` searchVulnerabilityByNamespace = ` WHERE n.name = $1 AND v.deleted_at IS NULL - ORDER BY v.name - LIMIT $2 offset $3` + AND v.id >= $2 + ORDER BY v.id + LIMIT $3` searchVulnerabilityFixedIn = ` SELECT vfif.version, f.id, f.Name diff --git a/database/pgsql/vulnerability.go b/database/pgsql/vulnerability.go index ade3a6f65d..035f14131a 100644 --- a/database/pgsql/vulnerability.go +++ b/database/pgsql/vulnerability.go @@ -28,22 +28,20 @@ import ( "github.com/guregu/null/zero" ) -func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, page int) ([]database.Vulnerability, error) { - return listVulnerabilities(pgSQL, namespaceName, limit, page) -} - -func listVulnerabilities(queryer Queryer, namespaceName string, limit int, page int) ([]database.Vulnerability, error) { +func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID int) ([]database.Vulnerability, int, error) { defer observeQueryTime("listVulnerabilities", "all", time.Now()) // Query. query := searchVulnerabilityBase + searchVulnerabilityByNamespace - rows, err := queryer.Query(query, namespaceName, limit, page*limit) + rows, err := pgSQL.Query(query, namespaceName, startID, limit+1) if err != nil { - return nil, handleError("searchVulnerabilityByNamespace", err) + return nil, -1, handleError("searchVulnerabilityByNamespace", err) } defer rows.Close() var vulns []database.Vulnerability + nextID := -1 + size := 0 // Scan query. for rows.Next() { var vulnerability database.Vulnerability @@ -59,17 +57,22 @@ func listVulnerabilities(queryer Queryer, namespaceName string, limit int, page &vulnerability.Metadata, ) if err != nil { - return nil, handleError("searchVulnerabilityByNamespace.Scan()", err) + return nil, -1, handleError("searchVulnerabilityByNamespace.Scan()", err) + } + size++ + if size > limit { + nextID = vulnerability.ID } else { vulns = append(vulns, vulnerability) } } if err := rows.Err(); err != nil { - return nil, handleError("searchVulnerabilityByNamespace.Rows()", err) + return nil, -1, handleError("searchVulnerabilityByNamespace.Rows()", err) } - return vulns, nil + fmt.Println(nextID) + return vulns, nextID, nil } func (pgSQL *pgSQL) FindVulnerability(namespaceName, name string) (database.Vulnerability, error) { From 27e5e42340a906fea453eaa166b962341d8cf8b6 Mon Sep 17 00:00:00 2001 From: liangchenye Date: Mon, 7 Mar 2016 14:52:26 +0800 Subject: [PATCH 3/4] use tokenMarshal/unmarshal in page encoding Signed-off-by: liangchenye --- api/v1/models.go | 46 +++++++++------------------------------------- api/v1/routes.go | 20 +++++++++++++++----- 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/api/v1/models.go b/api/v1/models.go index 7288528331..fc2a4e60d5 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "strconv" "time" "github.com/coreos/clair/database" @@ -216,7 +215,8 @@ func NotificationFromDatabaseModel(dbNotification database.VulnerabilityNotifica var nextPageStr string if nextPage != database.NoVulnerabilityNotificationPage { - nextPageStr = pageNumberToToken(nextPage, key) + nextPageBytes, _ := tokenMarshal(nextPage, key) + nextPageStr = string(nextPageBytes) } var created, notified, deleted string @@ -292,51 +292,23 @@ type FeatureEnvelope struct { Error *Error `json:"Error,omitempty"` } -func tokenToPageNumber(token, key string) (database.VulnerabilityNotificationPageNumber, error) { +func tokenUnmarshal(token string, key string, v interface{}) error { k, _ := fernet.DecodeKey(key) msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k}) if msg == nil { - return database.VulnerabilityNotificationPageNumber{}, errors.New("invalid or expired pagination token") + return errors.New("invalid or expired pagination token") } - page := database.VulnerabilityNotificationPageNumber{} - err := json.NewDecoder(bytes.NewBuffer(msg)).Decode(&page) - return page, err + return json.NewDecoder(bytes.NewBuffer(msg)).Decode(&v) } -func pageNumberToToken(page database.VulnerabilityNotificationPageNumber, key string) string { +func tokenMarshal(v interface{}, key string) ([]byte, error) { var buf bytes.Buffer - err := json.NewEncoder(&buf).Encode(page) + err := json.NewEncoder(&buf).Encode(v) if err != nil { - log.Fatal("failed to encode VulnerabilityNotificationPageNumber") + return nil, err } k, _ := fernet.DecodeKey(key) - tokenBytes, err := fernet.EncryptAndSign(buf.Bytes(), k) - if err != nil { - log.Fatal("failed to encrypt VulnerabilityNotificationpageNumber") - } - - return string(tokenBytes) -} - -func tokenToNumber(token, key string) (int, error) { - k, _ := fernet.DecodeKey(key) - msg := fernet.VerifyAndDecrypt([]byte(token), time.Hour, []*fernet.Key{k}) - if msg == nil { - return -1, errors.New("invalid or expired pagination token") - } - - page, err := strconv.Atoi(string(msg)) - return page, err -} - -func numberToToken(page int, key string) string { - k, _ := fernet.DecodeKey(key) - tokenBytes, err := fernet.EncryptAndSign([]byte(strconv.Itoa(page)), k) - if err != nil { - log.Fatal("failed to encrypt number") - } - - return string(tokenBytes) + return fernet.EncryptAndSign(buf.Bytes(), k) } diff --git a/api/v1/routes.go b/api/v1/routes.go index 0dc48572ec..af2d8b3f85 100644 --- a/api/v1/routes.go +++ b/api/v1/routes.go @@ -197,7 +197,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par if err != nil { writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid limit format: " + err.Error()}}) return getVulnerabilitiesRoute, http.StatusBadRequest - } else if limit <= 0 { + } else if limit < 0 { writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"limit value should not be less than zero"}}) return getVulnerabilitiesRoute, http.StatusBadRequest } @@ -205,7 +205,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par page := 0 pageStrs, pageExists := query["page"] if pageExists { - page, err = tokenToNumber(pageStrs[0], ctx.Config.PaginationKey) + err = tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page) if err != nil { writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) return getNotificationRoute, http.StatusBadRequest @@ -226,7 +226,12 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par var nextPageStr string if nextPage != -1 { - nextPageStr = numberToToken(nextPage, ctx.Config.PaginationKey) + nextPageBytes, err := tokenMarshal(nextPage, ctx.Config.PaginationKey) + if err != nil { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}}) + return getNotificationRoute, http.StatusBadRequest + } + nextPageStr = string(nextPageBytes) } writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns, NextPage: &nextPageStr}) @@ -434,14 +439,19 @@ func getNotification(w http.ResponseWriter, r *http.Request, p httprouter.Params page := database.VulnerabilityNotificationFirstPage pageStrs, pageExists := query["page"] if pageExists { - page, err = tokenToPageNumber(pageStrs[0], ctx.Config.PaginationKey) + err := tokenUnmarshal(pageStrs[0], ctx.Config.PaginationKey, &page) if err != nil { writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"invalid page format: " + err.Error()}}) return getNotificationRoute, http.StatusBadRequest } pageToken = pageStrs[0] } else { - pageToken = pageNumberToToken(page, ctx.Config.PaginationKey) + pageTokenBytes, err := tokenMarshal(page, ctx.Config.PaginationKey) + if err != nil { + writeResponse(w, r, http.StatusBadRequest, NotificationEnvelope{Error: &Error{"failed to marshal token: " + err.Error()}}) + return getNotificationRoute, http.StatusBadRequest + } + pageToken = string(pageTokenBytes) } dbNotification, nextPage, err := ctx.Store.GetNotification(p.ByName("notificationName"), limit, page) From 1a863a06cfe635f7461a2dd85a2dba1b2465fcfc Mon Sep 17 00:00:00 2001 From: liangchenye Date: Tue, 8 Mar 2016 10:24:29 +0800 Subject: [PATCH 4/4] remove the useless pointer of NextPage field; check namespace notfound error Signed-off-by: liangchenye --- api/v1/models.go | 2 +- api/v1/routes.go | 15 ++++++++++++--- database/pgsql/queries.go | 3 ++- database/pgsql/vulnerability.go | 10 +++++++++- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/api/v1/models.go b/api/v1/models.go index fc2a4e60d5..f3b3c5e08b 100644 --- a/api/v1/models.go +++ b/api/v1/models.go @@ -277,7 +277,7 @@ type NamespaceEnvelope struct { type VulnerabilityEnvelope struct { Vulnerability *Vulnerability `json:"Vulnerability,omitempty"` Vulnerabilities *[]Vulnerability `json:"Vulnerabilities,omitempty"` - NextPage *string `json:"NextPage,omitempty"` + NextPage string `json:"NextPage,omitempty"` Error *Error `json:"Error,omitempty"` } diff --git a/api/v1/routes.go b/api/v1/routes.go index af2d8b3f85..3edbe7691a 100644 --- a/api/v1/routes.go +++ b/api/v1/routes.go @@ -212,8 +212,17 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par } } - dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(p.ByName("namespaceName"), limit, page) - if err != nil { + namespace := p.ByName("namespaceName") + if namespace == "" { + writeResponse(w, r, http.StatusBadRequest, VulnerabilityEnvelope{Error: &Error{"namespace should not be empty"}}) + return getNotificationRoute, http.StatusBadRequest + } + + dbVulns, nextPage, err := ctx.Store.ListVulnerabilities(namespace, limit, page) + if err == cerrors.ErrNotFound { + writeResponse(w, r, http.StatusNotFound, VulnerabilityEnvelope{Error: &Error{err.Error()}}) + return getVulnerabilityRoute, http.StatusNotFound + } else if err != nil { writeResponse(w, r, http.StatusInternalServerError, VulnerabilityEnvelope{Error: &Error{err.Error()}}) return getVulnerabilitiesRoute, http.StatusInternalServerError } @@ -234,7 +243,7 @@ func getVulnerabilities(w http.ResponseWriter, r *http.Request, p httprouter.Par nextPageStr = string(nextPageBytes) } - writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns, NextPage: &nextPageStr}) + writeResponse(w, r, http.StatusOK, VulnerabilityEnvelope{Vulnerabilities: &vulns, NextPage: nextPageStr}) return getVulnerabilitiesRoute, http.StatusOK } diff --git a/database/pgsql/queries.go b/database/pgsql/queries.go index 0b67ecf53e..aba20d70cb 100644 --- a/database/pgsql/queries.go +++ b/database/pgsql/queries.go @@ -38,7 +38,8 @@ const ( UNION SELECT id FROM new_namespace` - listNamespace = `SELECT id, name FROM Namespace` + searchNamespace = `SELECT id FROM Namespace WHERE name = $1` + listNamespace = `SELECT id, name FROM Namespace` // feature.go soiFeature = ` diff --git a/database/pgsql/vulnerability.go b/database/pgsql/vulnerability.go index 035f14131a..74ee98282b 100644 --- a/database/pgsql/vulnerability.go +++ b/database/pgsql/vulnerability.go @@ -31,6 +31,15 @@ import ( func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID int) ([]database.Vulnerability, int, error) { defer observeQueryTime("listVulnerabilities", "all", time.Now()) + // Query Namespace. + var id int + err := pgSQL.QueryRow(searchNamespace, namespaceName).Scan(&id) + if err != nil { + return nil, -1, handleError("searchNamespace", err) + } else if id == 0 { + return nil, -1, cerrors.ErrNotFound + } + // Query. query := searchVulnerabilityBase + searchVulnerabilityByNamespace rows, err := pgSQL.Query(query, namespaceName, startID, limit+1) @@ -71,7 +80,6 @@ func (pgSQL *pgSQL) ListVulnerabilities(namespaceName string, limit int, startID return nil, -1, handleError("searchVulnerabilityByNamespace.Rows()", err) } - fmt.Println(nextID) return vulns, nextID, nil }