Skip to content

Commit 383b6d9

Browse files
authored
Merge pull request #1679 from gwenya/filters
Add filtering to all API collections
2 parents 44fb5ff + 7bfbea8 commit 383b6d9

19 files changed

+1290
-679
lines changed

cmd/incusd/api_project.go

Lines changed: 74 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/gorilla/mux"
1616

1717
incus "github.com/lxc/incus/v6/client"
18+
"github.com/lxc/incus/v6/internal/filter"
1819
"github.com/lxc/incus/v6/internal/jmap"
1920
"github.com/lxc/incus/v6/internal/server/auth"
2021
"github.com/lxc/incus/v6/internal/server/db"
@@ -73,6 +74,12 @@ var projectAccessCmd = APIEndpoint{
7374
// ---
7475
// produces:
7576
// - application/json
77+
// parameters:
78+
// - in: query
79+
// name: filter
80+
// description: Collection filter
81+
// type: string
82+
// example: default
7683
// responses:
7784
// "200":
7885
// description: API endpoints
@@ -109,59 +116,72 @@ var projectAccessCmd = APIEndpoint{
109116

110117
// swagger:operation GET /1.0/projects?recursion=1 projects projects_get_recursion1
111118
//
112-
// Get the projects
119+
// Get the projects
113120
//
114-
// Returns a list of projects (structs).
121+
// Returns a list of projects (structs).
115122
//
116-
// ---
117-
// produces:
118-
// - application/json
119-
// responses:
120-
// "200":
121-
// description: API endpoints
122-
// schema:
123-
// type: object
124-
// description: Sync response
125-
// properties:
126-
// type:
127-
// type: string
128-
// description: Response type
129-
// example: sync
130-
// status:
131-
// type: string
132-
// description: Status description
133-
// example: Success
134-
// status_code:
135-
// type: integer
136-
// description: Status code
137-
// example: 200
138-
// metadata:
139-
// type: array
140-
// description: List of projects
141-
// items:
142-
// $ref: "#/definitions/Project"
143-
// "403":
144-
// $ref: "#/responses/Forbidden"
145-
// "500":
146-
// $ref: "#/responses/InternalServerError"
123+
// ---
124+
// produces:
125+
// - application/json
126+
// parameters:
127+
// - in: query
128+
// name: filter
129+
// description: Collection filter
130+
// type: string
131+
// example: default
132+
// responses:
133+
// "200":
134+
// description: API endpoints
135+
// schema:
136+
// type: object
137+
// description: Sync response
138+
// properties:
139+
// type:
140+
// type: string
141+
// description: Response type
142+
// example: sync
143+
// status:
144+
// type: string
145+
// description: Status description
146+
// example: Success
147+
// status_code:
148+
// type: integer
149+
// description: Status code
150+
// example: 200
151+
// metadata:
152+
// type: array
153+
// description: List of projects
154+
// items:
155+
// $ref: "#/definitions/Project"
156+
// "403":
157+
// $ref: "#/responses/Forbidden"
158+
// "500":
159+
// $ref: "#/responses/InternalServerError"
160+
147161
func projectsGet(d *Daemon, r *http.Request) response.Response {
148162
s := d.State()
149163

150164
recursion := localUtil.IsRecursionRequest(r)
151165

166+
// Parse filter value.
167+
filterStr := r.FormValue("filter")
168+
clauses, err := filter.Parse(filterStr, filter.QueryOperatorSet())
169+
if err != nil {
170+
return response.BadRequest(fmt.Errorf("Invalid filter: %w", err))
171+
}
172+
152173
userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeProject)
153174
if err != nil {
154175
return response.InternalError(err)
155176
}
156177

157-
var result any
178+
filtered := make([]api.Project, 0)
158179
err = s.DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
159180
projects, err := cluster.GetProjects(ctx, tx.Tx())
160181
if err != nil {
161182
return err
162183
}
163184

164-
filtered := []api.Project{}
165185
for _, project := range projects {
166186
if !userHasPermission(auth.ObjectProject(project.Name)) {
167187
continue
@@ -177,18 +197,18 @@ func projectsGet(d *Daemon, r *http.Request) response.Response {
177197
return err
178198
}
179199

180-
filtered = append(filtered, *apiProject)
181-
}
200+
if clauses != nil && len(clauses.Clauses) > 0 {
201+
match, err := filter.Match(*apiProject, *clauses)
202+
if err != nil {
203+
return err
204+
}
182205

183-
if recursion {
184-
result = filtered
185-
} else {
186-
urls := make([]string, len(filtered))
187-
for i, p := range filtered {
188-
urls[i] = p.URL(version.APIVersion).String()
206+
if !match {
207+
continue
208+
}
189209
}
190210

191-
result = urls
211+
filtered = append(filtered, *apiProject)
192212
}
193213

194214
return nil
@@ -197,7 +217,16 @@ func projectsGet(d *Daemon, r *http.Request) response.Response {
197217
return response.SmartError(err)
198218
}
199219

200-
return response.SyncResponse(true, result)
220+
if recursion {
221+
return response.SyncResponse(true, filtered)
222+
}
223+
224+
urls := make([]string, len(filtered))
225+
for i, p := range filtered {
226+
urls[i] = p.URL(version.APIVersion).String()
227+
}
228+
229+
return response.SyncResponse(true, urls)
201230
}
202231

203232
// projectUsedBy returns a list of URLs for all instances, images, profiles,

cmd/incusd/certificates.go

Lines changed: 95 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/gorilla/mux"
1717

1818
incus "github.com/lxc/incus/v6/client"
19+
"github.com/lxc/incus/v6/internal/filter"
1920
internalInstance "github.com/lxc/incus/v6/internal/instance"
2021
"github.com/lxc/incus/v6/internal/server/auth"
2122
"github.com/lxc/incus/v6/internal/server/certificate"
@@ -62,6 +63,12 @@ var certificateCmd = APIEndpoint{
6263
// ---
6364
// produces:
6465
// - application/json
66+
// parameters:
67+
// - in: query
68+
// name: filter
69+
// description: Collection filter
70+
// type: string
71+
// example: default
6572
// responses:
6673
// "200":
6774
// description: API endpoints
@@ -98,52 +105,71 @@ var certificateCmd = APIEndpoint{
98105

99106
// swagger:operation GET /1.0/certificates?recursion=1 certificates certificates_get_recursion1
100107
//
101-
// Get the trusted certificates
108+
// Get the trusted certificates
102109
//
103-
// Returns a list of trusted certificates (structs).
110+
// Returns a list of trusted certificates (structs).
104111
//
105-
// ---
106-
// produces:
107-
// - application/json
108-
// responses:
109-
// "200":
110-
// description: API endpoints
111-
// schema:
112-
// type: object
113-
// description: Sync response
114-
// properties:
115-
// type:
116-
// type: string
117-
// description: Response type
118-
// example: sync
119-
// status:
120-
// type: string
121-
// description: Status description
122-
// example: Success
123-
// status_code:
124-
// type: integer
125-
// description: Status code
126-
// example: 200
127-
// metadata:
128-
// type: array
129-
// description: List of certificates
130-
// items:
131-
// $ref: "#/definitions/Certificate"
132-
// "403":
133-
// $ref: "#/responses/Forbidden"
134-
// "500":
135-
// $ref: "#/responses/InternalServerError"
112+
// ---
113+
// produces:
114+
// - application/json
115+
// parameters:
116+
// - in: query
117+
// name: filter
118+
// description: Collection filter
119+
// type: string
120+
// example: default
121+
// responses:
122+
// "200":
123+
// description: API endpoints
124+
// schema:
125+
// type: object
126+
// description: Sync response
127+
// properties:
128+
// type:
129+
// type: string
130+
// description: Response type
131+
// example: sync
132+
// status:
133+
// type: string
134+
// description: Status description
135+
// example: Success
136+
// status_code:
137+
// type: integer
138+
// description: Status code
139+
// example: 200
140+
// metadata:
141+
// type: array
142+
// description: List of certificates
143+
// items:
144+
// $ref: "#/definitions/Certificate"
145+
// "403":
146+
// $ref: "#/responses/Forbidden"
147+
// "500":
148+
// $ref: "#/responses/InternalServerError"
149+
136150
func certificatesGet(d *Daemon, r *http.Request) response.Response {
137-
recursion := localUtil.IsRecursionRequest(r)
138151
s := d.State()
139152

140153
userHasPermission, err := s.Authorizer.GetPermissionChecker(r.Context(), r, auth.EntitlementCanView, auth.ObjectTypeCertificate)
141154
if err != nil {
142155
return response.SmartError(err)
143156
}
144157

145-
if recursion {
146-
var certResponses []api.Certificate
158+
recursion := localUtil.IsRecursionRequest(r)
159+
160+
// Parse filter value.
161+
filterStr := r.FormValue("filter")
162+
clauses, err := filter.Parse(filterStr, filter.QueryOperatorSet())
163+
if err != nil {
164+
return response.BadRequest(fmt.Errorf("Invalid filter: %w", err))
165+
}
166+
167+
mustLoadObjects := recursion || (clauses != nil && len(clauses.Clauses) > 0)
168+
169+
linkResults := make([]string, 0)
170+
fullResults := make([]api.Certificate, 0)
171+
172+
if mustLoadObjects {
147173
var baseCerts []dbCluster.Certificate
148174
var err error
149175
err = d.State().DB.Cluster.Transaction(r.Context(), func(ctx context.Context, tx *db.ClusterTx) error {
@@ -152,7 +178,6 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response {
152178
return err
153179
}
154180

155-
certResponses = make([]api.Certificate, 0, len(baseCerts))
156181
for _, baseCert := range baseCerts {
157182
if !userHasPermission(auth.ObjectCertificate(baseCert.Fingerprint)) {
158183
continue
@@ -163,38 +188,52 @@ func certificatesGet(d *Daemon, r *http.Request) response.Response {
163188
return err
164189
}
165190

166-
certResponses = append(certResponses, *apiCert)
191+
if clauses != nil && len(clauses.Clauses) > 0 {
192+
match, err := filter.Match(*apiCert, *clauses)
193+
if err != nil {
194+
return err
195+
}
196+
197+
if !match {
198+
continue
199+
}
200+
}
201+
202+
fullResults = append(fullResults, *apiCert)
203+
204+
certificateURL := fmt.Sprintf("/%s/certificates/%s", version.APIVersion, apiCert.Fingerprint)
205+
linkResults = append(linkResults, certificateURL)
167206
}
168207

169208
return nil
170209
})
171210
if err != nil {
172211
return response.SmartError(err)
173212
}
213+
} else {
214+
trustedCertificates, err := d.getTrustedCertificates()
215+
if err != nil {
216+
return response.SmartError(err)
217+
}
174218

175-
return response.SyncResponse(true, certResponses)
176-
}
177-
178-
body := []string{}
179-
180-
trustedCertificates, err := d.getTrustedCertificates()
181-
if err != nil {
182-
return response.SmartError(err)
183-
}
219+
for _, certs := range trustedCertificates {
220+
for _, cert := range certs {
221+
fingerprint := localtls.CertFingerprint(&cert)
222+
if !userHasPermission(auth.ObjectCertificate(fingerprint)) {
223+
continue
224+
}
184225

185-
for _, certs := range trustedCertificates {
186-
for _, cert := range certs {
187-
fingerprint := localtls.CertFingerprint(&cert)
188-
if !userHasPermission(auth.ObjectCertificate(fingerprint)) {
189-
continue
226+
certificateURL := fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint)
227+
linkResults = append(linkResults, certificateURL)
190228
}
191-
192-
certificateURL := fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint)
193-
body = append(body, certificateURL)
194229
}
195230
}
196231

197-
return response.SyncResponse(true, body)
232+
if recursion {
233+
return response.SyncResponse(true, fullResults)
234+
}
235+
236+
return response.SyncResponse(true, linkResults)
198237
}
199238

200239
func updateCertificateCache(d *Daemon) {

0 commit comments

Comments
 (0)