• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.8
/backend/services/deviceauth/api/http/api_devauth.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package http
15

16
import (
17
        "context"
18
        "encoding/json"
19
        "net/http"
20
        "strings"
21
        "time"
22

23
        "github.com/ant0ine/go-json-rest/rest"
24
        "github.com/pkg/errors"
25

26
        "github.com/mendersoftware/mender-server/pkg/accesslog"
27
        ctxhttpheader "github.com/mendersoftware/mender-server/pkg/context/httpheader"
28
        "github.com/mendersoftware/mender-server/pkg/identity"
29
        "github.com/mendersoftware/mender-server/pkg/log"
30
        "github.com/mendersoftware/mender-server/pkg/requestid"
31
        "github.com/mendersoftware/mender-server/pkg/requestlog"
32
        "github.com/mendersoftware/mender-server/pkg/rest_utils"
33

34
        "github.com/mendersoftware/mender-server/services/deviceauth/access"
35
        "github.com/mendersoftware/mender-server/services/deviceauth/cache"
36
        "github.com/mendersoftware/mender-server/services/deviceauth/devauth"
37
        "github.com/mendersoftware/mender-server/services/deviceauth/jwt"
38
        "github.com/mendersoftware/mender-server/services/deviceauth/model"
39
        "github.com/mendersoftware/mender-server/services/deviceauth/store"
40
        "github.com/mendersoftware/mender-server/services/deviceauth/utils"
41
)
42

43
const (
44
        uriAuthReqs = "/api/devices/v1/authentication/auth_requests"
45

46
        // internal API
47
        uriAlive              = "/api/internal/v1/devauth/alive"
48
        uriHealth             = "/api/internal/v1/devauth/health"
49
        uriTokenVerify        = "/api/internal/v1/devauth/tokens/verify"
50
        uriTenantLimit        = "/api/internal/v1/devauth/tenant/#id/limits/#name"
51
        uriTokens             = "/api/internal/v1/devauth/tokens"
52
        uriTenants            = "/api/internal/v1/devauth/tenants"
53
        uriTenantDevice       = "/api/internal/v1/devauth/tenants/#tid/devices/#did"
54
        uriTenantDeviceStatus = "/api/internal/v1/devauth/tenants/#tid/devices/#did/status"
55
        uriTenantDevices      = "/api/internal/v1/devauth/tenants/#tid/devices"
56
        uriTenantDevicesCount = "/api/internal/v1/devauth/tenants/#tid/devices/count"
57

58
        // management API v2
59
        v2uriDevices             = "/api/management/v2/devauth/devices"
60
        v2uriDevicesCount        = "/api/management/v2/devauth/devices/count"
61
        v2uriDevicesSearch       = "/api/management/v2/devauth/devices/search"
62
        v2uriDevice              = "/api/management/v2/devauth/devices/#id"
63
        v2uriDeviceAuthSet       = "/api/management/v2/devauth/devices/#id/auth/#aid"
64
        v2uriDeviceAuthSetStatus = "/api/management/v2/devauth/devices/#id/auth/#aid/status"
65
        v2uriToken               = "/api/management/v2/devauth/tokens/#id"
66
        v2uriDevicesLimit        = "/api/management/v2/devauth/limits/#name"
67

68
        HdrAuthReqSign = "X-MEN-Signature"
69
)
70

71
func init() {
3✔
72
        rest.ErrorFieldName = "error"
3✔
73
}
3✔
74

75
const (
76
        defaultTimeout = time.Second * 5
77
)
78

79
var (
80
        ErrIncorrectStatus = errors.New("incorrect device status")
81
        ErrNoAuthHeader    = errors.New("no authorization header")
82
)
83

84
type DevAuthApiHandlers struct {
85
        devAuth devauth.App
86
        db      store.DataStore
87
}
88

89
type DevAuthApiStatus struct {
90
        Status string `json:"status"`
91
}
92

93
func NewDevAuthApiHandlers(devAuth devauth.App, db store.DataStore) ApiHandler {
1✔
94
        return &DevAuthApiHandlers{
1✔
95
                devAuth: devAuth,
1✔
96
                db:      db,
1✔
97
        }
1✔
98
}
1✔
99

100
func wrapMiddleware(middleware rest.Middleware, routes ...*rest.Route) []*rest.Route {
1✔
101
        for _, route := range routes {
2✔
102
                route.Func = middleware.MiddlewareFunc(route.Func)
1✔
103
        }
1✔
104
        return routes
1✔
105
}
106

107
func (d *DevAuthApiHandlers) Build() (http.Handler, error) {
1✔
108
        identityMiddleware := &identity.IdentityMiddleware{
1✔
109
                UpdateLogger: true,
1✔
110
        }
1✔
111
        internalRoutes := []*rest.Route{
1✔
112
                rest.Get(uriAlive, d.AliveHandler),
1✔
113
                rest.Get(uriHealth, d.HealthCheckHandler),
1✔
114
                rest.Get(uriTokenVerify, identityMiddleware.MiddlewareFunc(
1✔
115
                        d.VerifyTokenHandler,
1✔
116
                )),
1✔
117
                rest.Post(uriTokenVerify, identityMiddleware.MiddlewareFunc(
1✔
118
                        d.VerifyTokenHandler,
1✔
119
                )),
1✔
120
                rest.Delete(uriTokens, d.DeleteTokensHandler),
1✔
121

1✔
122
                rest.Put(uriTenantLimit, d.PutTenantLimitHandler),
1✔
123
                rest.Get(uriTenantLimit, d.GetTenantLimitHandler),
1✔
124
                rest.Delete(uriTenantLimit, d.DeleteTenantLimitHandler),
1✔
125

1✔
126
                rest.Post(uriTenants, d.ProvisionTenantHandler),
1✔
127
                rest.Get(uriTenantDeviceStatus, d.GetTenantDeviceStatus),
1✔
128
                rest.Get(uriTenantDevices, d.GetTenantDevicesHandler),
1✔
129
                rest.Get(uriTenantDevicesCount, d.GetTenantDevicesCountHandler),
1✔
130
                rest.Delete(uriTenantDevice, d.DeleteDeviceHandler),
1✔
131
        }
1✔
132
        publicRoutes := []*rest.Route{
1✔
133
                // Devices API
1✔
134
                rest.Post(uriAuthReqs, d.SubmitAuthRequestHandler),
1✔
135

1✔
136
                // API v2
1✔
137
                rest.Get(v2uriDevicesCount, d.GetDevicesCountHandler),
1✔
138
                rest.Get(v2uriDevices, d.GetDevicesV2Handler),
1✔
139
                rest.Post(v2uriDevicesSearch, d.SearchDevicesV2Handler),
1✔
140
                rest.Post(v2uriDevices, d.PostDevicesV2Handler),
1✔
141
                rest.Get(v2uriDevice, d.GetDeviceV2Handler),
1✔
142
                rest.Delete(v2uriDevice, d.DecommissionDeviceHandler),
1✔
143
                rest.Delete(v2uriDeviceAuthSet, d.DeleteDeviceAuthSetHandler),
1✔
144
                rest.Put(v2uriDeviceAuthSetStatus, d.UpdateDeviceStatusHandler),
1✔
145
                rest.Get(v2uriDeviceAuthSetStatus, d.GetAuthSetStatusHandler),
1✔
146
                rest.Delete(v2uriToken, d.DeleteTokenHandler),
1✔
147
                rest.Get(v2uriDevicesLimit, d.GetLimitHandler),
1✔
148
        }
1✔
149
        publicRoutes = wrapMiddleware(identityMiddleware, publicRoutes...)
1✔
150

1✔
151
        routes := append(publicRoutes, internalRoutes...)
1✔
152

1✔
153
        app, err := rest.MakeRouter(
1✔
154
                // augment routes with OPTIONS handler
1✔
155
                AutogenOptionsRoutes(routes, AllowHeaderOptionsGenerator)...,
1✔
156
        )
1✔
157
        if err != nil {
1✔
158
                return nil, errors.Wrap(err, "failed to create router")
×
159
        }
×
160

161
        api := rest.NewApi()
1✔
162
        api.SetApp(app)
1✔
163
        api.Use(
1✔
164
                &requestlog.RequestLogMiddleware{},
1✔
165
                &requestid.RequestIdMiddleware{},
1✔
166
                &accesslog.AccessLogMiddleware{
1✔
167
                        Format: accesslog.SimpleLogFormat,
1✔
168
                        DisableLog: func(statusCode int, r *rest.Request) bool {
2✔
169
                                if statusCode < 300 &&
1✔
170
                                        (r.Request.URL.Path == uriAlive ||
1✔
171
                                                r.Request.URL.Path == uriHealth ||
1✔
172
                                                r.Request.URL.Path == uriTokenVerify) {
2✔
173
                                        return true
1✔
174
                                }
1✔
175
                                return false
1✔
176
                        },
177
                },
178
                // verifies the request Content-Type header
179
                // The expected Content-Type is 'application/json'
180
                // if the content is non-null
181
                &rest.ContentTypeCheckerMiddleware{},
182
        )
183

184
        return api.MakeHandler(), nil
1✔
185
}
186

187
func (d *DevAuthApiHandlers) AliveHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
188
        w.WriteHeader(http.StatusNoContent)
1✔
189
}
1✔
190

191
func (d *DevAuthApiHandlers) HealthCheckHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
192
        ctx := r.Context()
1✔
193
        l := log.FromContext(ctx)
1✔
194
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
1✔
195
        defer cancel()
1✔
196

1✔
197
        err := d.devAuth.HealthCheck(ctx)
1✔
198
        if err != nil {
2✔
199
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusServiceUnavailable)
1✔
200
                return
1✔
201
        }
1✔
202
        w.WriteHeader(http.StatusNoContent)
1✔
203
}
204

205
func (d *DevAuthApiHandlers) SubmitAuthRequestHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
206
        var authreq model.AuthReq
1✔
207

1✔
208
        ctx := r.Context()
1✔
209

1✔
210
        l := log.FromContext(ctx)
1✔
211

1✔
212
        //validate req body by reading raw content manually
1✔
213
        //(raw body will be needed later, DecodeJsonPayload would
1✔
214
        //unmarshal and close it)
1✔
215
        body, err := utils.ReadBodyRaw(r)
1✔
216
        if err != nil {
1✔
217
                err = errors.Wrap(err, "failed to decode auth request")
×
218
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
×
219
                return
×
220
        }
×
221

222
        err = json.Unmarshal(body, &authreq)
1✔
223
        if err != nil {
2✔
224
                err = errors.Wrap(err, "failed to decode auth request")
1✔
225
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
226
                return
1✔
227
        }
1✔
228

229
        err = authreq.Validate()
1✔
230
        if err != nil {
2✔
231
                err = errors.Wrap(err, "invalid auth request")
1✔
232
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
233
                return
1✔
234
        }
1✔
235

236
        //verify signature
237
        signature := r.Header.Get(HdrAuthReqSign)
1✔
238
        if signature == "" {
2✔
239
                rest_utils.RestErrWithLog(
1✔
240
                        w,
1✔
241
                        r,
1✔
242
                        l,
1✔
243
                        errors.New("missing request signature header"),
1✔
244
                        http.StatusBadRequest,
1✔
245
                )
1✔
246
                return
1✔
247
        }
1✔
248

249
        token, err := d.devAuth.SubmitAuthRequest(ctx, &authreq)
1✔
250
        if err != nil {
2✔
251
                if devauth.IsErrDevAuthUnauthorized(err) {
2✔
252
                        rest_utils.RestErrWithWarningMsg(w, r, l, err,
1✔
253
                                http.StatusUnauthorized, errors.Cause(err).Error())
1✔
254
                        return
1✔
255
                } else if devauth.IsErrDevAuthBadRequest(err) {
1✔
256
                        rest_utils.RestErrWithWarningMsg(w, r, l, err,
×
257
                                http.StatusBadRequest, errors.Cause(err).Error())
×
258
                        return
×
259
                }
×
260
        }
261

262
        switch err {
1✔
263
        case nil:
1✔
264
                err = utils.VerifyAuthReqSign(signature, authreq.PubKeyStruct, body)
1✔
265
                if err != nil {
2✔
266
                        rest_utils.RestErrWithLogMsg(
1✔
267
                                w,
1✔
268
                                r,
1✔
269
                                l,
1✔
270
                                err,
1✔
271
                                http.StatusUnauthorized,
1✔
272
                                "signature verification failed",
1✔
273
                        )
1✔
274
                        return
1✔
275
                }
1✔
276
                w.Header().Set("Content-Type", "application/jwt")
1✔
277
                _, _ = w.(http.ResponseWriter).Write([]byte(token))
1✔
278
                return
1✔
279
        case devauth.ErrDevIdAuthIdMismatch, devauth.ErrMaxDeviceCountReached:
×
280
                // error is always set to unauthorized, client does not need to
×
281
                // know why
×
282
                rest_utils.RestErrWithWarningMsg(w, r, l, devauth.ErrDevAuthUnauthorized,
×
283
                        http.StatusUnauthorized, "unauthorized")
×
284
                return
×
285
        default:
×
286
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
287
                return
×
288
        }
289
}
290

291
func (d *DevAuthApiHandlers) PostDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
1✔
292
        ctx := r.Context()
1✔
293

1✔
294
        l := log.FromContext(ctx)
1✔
295

1✔
296
        req, err := parsePreAuthReq(r.Body)
1✔
297
        if err != nil {
2✔
298
                err = errors.Wrap(err, "failed to decode preauth request")
1✔
299
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
300
                return
1✔
301
        }
1✔
302

303
        reqDbModel, err := req.getDbModel()
1✔
304
        if err != nil {
1✔
305
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
306
                return
×
307
        }
×
308

309
        device, err := d.devAuth.PreauthorizeDevice(ctx, reqDbModel)
1✔
310
        switch err {
1✔
311
        case nil:
1✔
312
                w.Header().Set("Location", "devices/"+reqDbModel.DeviceId)
1✔
313
                w.WriteHeader(http.StatusCreated)
1✔
314
        case devauth.ErrDeviceExists:
1✔
315
                l.Error(err)
1✔
316
                w.WriteHeader(http.StatusConflict)
1✔
317
                _ = w.WriteJson(device)
1✔
318
        default:
1✔
319
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
320
        }
321
}
322

323
func (d *DevAuthApiHandlers) SearchDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
1✔
324
        ctx := r.Context()
1✔
325
        l := log.FromContext(ctx)
1✔
326

1✔
327
        page, perPage, err := rest_utils.ParsePagination(r)
1✔
328
        if err != nil {
2✔
329
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
330
                return
1✔
331
        }
1✔
332
        fltr := model.DeviceFilter{}
1✔
333

1✔
334
        switch strings.ToLower(r.Header.Get("Content-Type")) {
1✔
335
        case "application/json", "":
1✔
336
                err := r.DecodeJsonPayload(&fltr)
1✔
337
                if err != nil {
2✔
338
                        err = errors.Wrap(err, "api: malformed request body")
1✔
339
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
340
                        return
1✔
341
                }
1✔
342

343
        case "application/x-www-form-urlencoded":
1✔
344
                if err = r.ParseForm(); err != nil {
1✔
345
                        err = errors.Wrap(err, "api: malformed query parameters")
×
346
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
×
347
                        return
×
348
                }
×
349
                if err = fltr.ParseForm(r.Form); err != nil {
1✔
350
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
×
351
                        return
×
352
                }
×
353

354
        default:
×
355
                rest_utils.RestErrWithLog(w, r, l, errors.Errorf(
×
356
                        "Content-Type '%s' not supported",
×
357
                        r.Header.Get("Content-Type"),
×
358
                ), http.StatusUnsupportedMediaType)
×
359
                return
×
360
        }
361

362
        skip := (page - 1) * perPage
1✔
363
        limit := perPage + 1
1✔
364
        devs, err := d.devAuth.GetDevices(ctx, uint(skip), uint(limit), fltr)
1✔
365
        if err != nil {
2✔
366
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
367
                return
1✔
368
        }
1✔
369

370
        numDevs := len(devs)
1✔
371
        hasNext := false
1✔
372
        if uint64(numDevs) > perPage {
2✔
373
                hasNext = true
1✔
374
                numDevs = int(perPage)
1✔
375
        }
1✔
376

377
        links := rest_utils.MakePageLinkHdrs(r, page, perPage, hasNext)
1✔
378

1✔
379
        for _, l := range links {
2✔
380
                w.Header().Add("Link", l)
1✔
381
        }
1✔
382

383
        _ = w.WriteJson(devs[:numDevs])
1✔
384
}
385

386
func (d *DevAuthApiHandlers) GetDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
1✔
387
        r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
1✔
388
        d.SearchDevicesV2Handler(w, r)
1✔
389
}
1✔
390

391
func (d *DevAuthApiHandlers) GetDevicesCountHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
392
        ctx := r.Context()
1✔
393
        l := log.FromContext(ctx)
1✔
394

1✔
395
        status := r.URL.Query().Get("status")
1✔
396

1✔
397
        switch status {
1✔
398
        case model.DevStatusAccepted,
399
                model.DevStatusRejected,
400
                model.DevStatusPending,
401
                model.DevStatusPreauth,
402
                model.DevStatusNoAuth,
403
                "":
1✔
404
        default:
1✔
405
                rest_utils.RestErrWithLog(
1✔
406
                        w,
1✔
407
                        r,
1✔
408
                        l,
1✔
409
                        errors.New("status must be one of: pending, accepted, rejected, preauthorized, noauth"),
1✔
410
                        http.StatusBadRequest,
1✔
411
                )
1✔
412
                return
1✔
413
        }
414

415
        count, err := d.devAuth.GetDevCountByStatus(ctx, status)
1✔
416

1✔
417
        if err != nil {
2✔
418
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
419
                return
1✔
420
        }
1✔
421

422
        _ = w.WriteJson(model.Count{Count: count})
1✔
423
}
424

425
func (d *DevAuthApiHandlers) GetDeviceV2Handler(w rest.ResponseWriter, r *rest.Request) {
1✔
426

1✔
427
        ctx := r.Context()
1✔
428

1✔
429
        l := log.FromContext(ctx)
1✔
430

1✔
431
        devId := r.PathParam("id")
1✔
432

1✔
433
        dev, err := d.devAuth.GetDevice(ctx, devId)
1✔
434
        switch {
1✔
435
        case err == store.ErrDevNotFound:
1✔
436
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
1✔
437
        case dev != nil:
1✔
438
                _ = w.WriteJson(dev)
1✔
439
        default:
1✔
440
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
441
        }
442
}
443

444
func (d *DevAuthApiHandlers) DecommissionDeviceHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
445

1✔
446
        ctx := r.Context()
1✔
447

1✔
448
        l := log.FromContext(ctx)
1✔
449

1✔
450
        devId := r.PathParam("id")
1✔
451

1✔
452
        if err := d.devAuth.DecommissionDevice(ctx, devId); err != nil {
2✔
453
                if err == store.ErrDevNotFound {
2✔
454
                        w.WriteHeader(http.StatusNotFound)
1✔
455
                        return
1✔
456
                }
1✔
457
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
458
                return
1✔
459
        }
460

461
        w.WriteHeader(http.StatusNoContent)
1✔
462
}
463

464
func (d *DevAuthApiHandlers) DeleteDeviceAuthSetHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
465

1✔
466
        ctx := r.Context()
1✔
467

1✔
468
        l := log.FromContext(ctx)
1✔
469

1✔
470
        devId := r.PathParam("id")
1✔
471
        authId := r.PathParam("aid")
1✔
472

1✔
473
        if err := d.devAuth.DeleteAuthSet(ctx, devId, authId); err != nil {
2✔
474
                if err == store.ErrAuthSetNotFound {
2✔
475
                        w.WriteHeader(http.StatusNotFound)
1✔
476
                        return
1✔
477
                }
1✔
478
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
479
                return
1✔
480
        }
481

482
        w.WriteHeader(http.StatusNoContent)
1✔
483
}
484

485
func (d *DevAuthApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
486
        ctx := r.Context()
1✔
487

1✔
488
        l := log.FromContext(ctx)
1✔
489

1✔
490
        tokenID := r.PathParam("id")
1✔
491
        err := d.devAuth.RevokeToken(ctx, tokenID)
1✔
492
        if err != nil {
2✔
493
                if err == store.ErrTokenNotFound ||
1✔
494
                        err == devauth.ErrInvalidAuthSetID {
2✔
495
                        w.WriteHeader(http.StatusNotFound)
1✔
496
                        return
1✔
497
                }
1✔
498
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
499
                return
1✔
500
        }
501

502
        w.WriteHeader(http.StatusNoContent)
1✔
503
}
504

505
func (d *DevAuthApiHandlers) VerifyTokenHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
506
        ctx := r.Context()
1✔
507

1✔
508
        l := log.FromContext(ctx)
1✔
509

1✔
510
        tokenStr, err := extractToken(r.Header)
1✔
511
        if err != nil {
2✔
512
                rest_utils.RestErrWithLog(w, r, l, ErrNoAuthHeader, http.StatusUnauthorized)
1✔
513
                return
1✔
514
        }
1✔
515

516
        ctx = ctxhttpheader.WithContext(ctx,
1✔
517
                r.Header,
1✔
518
                "X-Forwarded-Method",
1✔
519
                "X-Forwarded-Uri")
1✔
520

1✔
521
        // verify token
1✔
522
        err = d.devAuth.VerifyToken(ctx, tokenStr)
1✔
523
        code := http.StatusOK
1✔
524
        if err != nil {
2✔
525
                switch e := errors.Cause(err); e {
1✔
526
                case jwt.ErrTokenExpired:
1✔
527
                        code = http.StatusForbidden
1✔
528
                case store.ErrTokenNotFound, store.ErrAuthSetNotFound, jwt.ErrTokenInvalid:
1✔
529
                        code = http.StatusUnauthorized
1✔
530
                case cache.ErrTooManyRequests:
1✔
531
                        code = http.StatusTooManyRequests
1✔
532
                default:
1✔
533
                        if _, ok := e.(access.PermissionError); ok {
1✔
534
                                rest_utils.RestErrWithLog(w, r, l, e, http.StatusForbidden)
×
535
                        } else {
1✔
536
                                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
537
                        }
1✔
538
                        return
1✔
539
                }
540
                l.Error(err)
1✔
541
        }
542

543
        w.WriteHeader(code)
1✔
544
}
545

546
func (d *DevAuthApiHandlers) UpdateDeviceStatusHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
547
        ctx := r.Context()
1✔
548

1✔
549
        l := log.FromContext(ctx)
1✔
550

1✔
551
        devid := r.PathParam("id")
1✔
552
        authid := r.PathParam("aid")
1✔
553

1✔
554
        var status DevAuthApiStatus
1✔
555
        err := r.DecodeJsonPayload(&status)
1✔
556
        if err != nil {
2✔
557
                err = errors.Wrap(err, "failed to decode status data")
1✔
558
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
559
                return
1✔
560
        }
1✔
561

562
        if err := statusValidate(&status); err != nil {
2✔
563
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
564
                return
1✔
565
        }
1✔
566

567
        if status.Status == model.DevStatusAccepted {
2✔
568
                err = d.devAuth.AcceptDeviceAuth(ctx, devid, authid)
1✔
569
        } else if status.Status == model.DevStatusRejected {
3✔
570
                err = d.devAuth.RejectDeviceAuth(ctx, devid, authid)
1✔
571
        } else if status.Status == model.DevStatusPending {
3✔
572
                err = d.devAuth.ResetDeviceAuth(ctx, devid, authid)
1✔
573
        }
1✔
574
        if err != nil {
2✔
575
                switch err {
1✔
576
                case store.ErrDevNotFound, store.ErrAuthSetNotFound:
1✔
577
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
1✔
578
                case devauth.ErrDevIdAuthIdMismatch, devauth.ErrDevAuthBadRequest:
1✔
579
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
580
                case devauth.ErrMaxDeviceCountReached:
1✔
581
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
1✔
582
                default:
1✔
583
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
584
                }
585
                return
1✔
586
        }
587

588
        w.WriteHeader(http.StatusNoContent)
1✔
589
}
590

591
type LimitValue struct {
592
        Limit uint64 `json:"limit"`
593
}
594

595
func (d *DevAuthApiHandlers) PutTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
596
        ctx := r.Context()
1✔
597

1✔
598
        l := log.FromContext(ctx)
1✔
599

1✔
600
        tenantId := r.PathParam("id")
1✔
601
        reqLimitName := r.PathParam("name")
1✔
602

1✔
603
        if !model.IsValidLimit(reqLimitName) {
2✔
604
                rest_utils.RestErrWithLog(w, r, l,
1✔
605
                        errors.Errorf("unsupported limit %v", reqLimitName),
1✔
606
                        http.StatusBadRequest)
1✔
607
                return
1✔
608
        }
1✔
609

610
        var value LimitValue
1✔
611
        err := r.DecodeJsonPayload(&value)
1✔
612
        if err != nil {
2✔
613
                err = errors.Wrap(err, "failed to decode limit request")
1✔
614
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
615
                return
1✔
616
        }
1✔
617

618
        limit := model.Limit{
1✔
619
                Value: value.Limit,
1✔
620
                Name:  reqLimitName,
1✔
621
        }
1✔
622

1✔
623
        if err := d.devAuth.SetTenantLimit(ctx, tenantId, limit); err != nil {
2✔
624
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
625
                return
1✔
626
        }
1✔
627

628
        w.WriteHeader(http.StatusNoContent)
1✔
629
}
630

631
func (d *DevAuthApiHandlers) DeleteTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
632
        ctx := r.Context()
1✔
633

1✔
634
        l := log.FromContext(ctx)
1✔
635

1✔
636
        tenantId := r.PathParam("id")
1✔
637
        reqLimitName := r.PathParam("name")
1✔
638

1✔
639
        if !model.IsValidLimit(reqLimitName) {
2✔
640
                rest_utils.RestErrWithLog(w, r, l,
1✔
641
                        errors.Errorf("unsupported limit %v", reqLimitName),
1✔
642
                        http.StatusBadRequest)
1✔
643
                return
1✔
644
        }
1✔
645

646
        if err := d.devAuth.DeleteTenantLimit(ctx, tenantId, reqLimitName); err != nil {
2✔
647
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
648
                return
1✔
649
        }
1✔
650

651
        w.WriteHeader(http.StatusNoContent)
1✔
652
}
653

654
func (d *DevAuthApiHandlers) GetTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
655
        ctx := r.Context()
1✔
656

1✔
657
        l := log.FromContext(ctx)
1✔
658

1✔
659
        tenantId := r.PathParam("id")
1✔
660
        limitName := r.PathParam("name")
1✔
661

1✔
662
        if !model.IsValidLimit(limitName) {
2✔
663
                rest_utils.RestErrWithLog(w, r, l,
1✔
664
                        errors.Errorf("unsupported limit %v", limitName),
1✔
665
                        http.StatusBadRequest)
1✔
666
                return
1✔
667
        }
1✔
668

669
        lim, err := d.devAuth.GetTenantLimit(ctx, limitName, tenantId)
1✔
670
        if err != nil {
2✔
671
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
672
                return
1✔
673
        }
1✔
674

675
        _ = w.WriteJson(LimitValue{lim.Value})
1✔
676
}
677

678
func (d *DevAuthApiHandlers) GetLimitHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
679
        ctx := r.Context()
1✔
680

1✔
681
        l := log.FromContext(ctx)
1✔
682

1✔
683
        name := r.PathParam("name")
1✔
684

1✔
685
        if !model.IsValidLimit(name) {
2✔
686
                rest_utils.RestErrWithLog(w, r, l,
1✔
687
                        errors.Errorf("unsupported limit %v", name),
1✔
688
                        http.StatusBadRequest)
1✔
689
                return
1✔
690
        }
1✔
691

692
        lim, err := d.devAuth.GetLimit(ctx, name)
1✔
693
        if err != nil {
2✔
694
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
695
                return
1✔
696
        }
1✔
697

698
        _ = w.WriteJson(LimitValue{lim.Value})
1✔
699
}
700

701
func (d *DevAuthApiHandlers) DeleteTokensHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
702

1✔
703
        ctx := r.Context()
1✔
704

1✔
705
        l := log.FromContext(ctx)
1✔
706

1✔
707
        tenantId := r.URL.Query().Get("tenant_id")
1✔
708
        if tenantId == "" {
2✔
709
                rest_utils.RestErrWithLog(
1✔
710
                        w,
1✔
711
                        r,
1✔
712
                        l,
1✔
713
                        errors.New("tenant_id must be provided"),
1✔
714
                        http.StatusBadRequest,
1✔
715
                )
1✔
716
                return
1✔
717
        }
1✔
718
        devId := r.URL.Query().Get("device_id")
1✔
719

1✔
720
        err := d.devAuth.DeleteTokens(ctx, tenantId, devId)
1✔
721
        switch err {
1✔
722
        case nil:
1✔
723
                w.WriteHeader(http.StatusNoContent)
1✔
724
        default:
1✔
725
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
726
        }
727
}
728

UNCOV
729
func (d *DevAuthApiHandlers) GetAuthSetStatusHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
730
        ctx := r.Context()
×
UNCOV
731
        l := log.FromContext(ctx)
×
UNCOV
732

×
UNCOV
733
        devid := r.PathParam("id")
×
UNCOV
734
        authid := r.PathParam("aid")
×
UNCOV
735

×
UNCOV
736
        // get authset directly from store
×
UNCOV
737
        aset, err := d.db.GetAuthSetById(ctx, authid)
×
UNCOV
738
        switch err {
×
UNCOV
739
        case nil:
×
UNCOV
740
                _ = w.WriteJson(&model.Status{Status: aset.Status})
×
UNCOV
741
        case store.ErrDevNotFound, store.ErrAuthSetNotFound:
×
UNCOV
742
                rest_utils.RestErrWithLog(w, r, l, store.ErrAuthSetNotFound, http.StatusNotFound)
×
743
        default:
×
744
                rest_utils.RestErrWithLogInternal(w, r, l,
×
745
                        errors.Wrapf(err,
×
746
                                "failed to fetch auth set %s for device %s",
×
747
                                authid, devid))
×
748
        }
749
}
750

751
func (d *DevAuthApiHandlers) ProvisionTenantHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
752
        // NOTE: This handler was used to initialize database collections. This is no longer
1✔
753
        //       needed after migration 2.0.0.
1✔
754
        w.WriteHeader(http.StatusCreated)
1✔
755
}
1✔
756

757
func (d *DevAuthApiHandlers) GetTenantDeviceStatus(w rest.ResponseWriter, r *rest.Request) {
1✔
758
        ctx := r.Context()
1✔
759

1✔
760
        l := log.FromContext(ctx)
1✔
761

1✔
762
        tid := r.PathParam("tid")
1✔
763
        did := r.PathParam("did")
1✔
764

1✔
765
        if did == "" {
2✔
766
                rest_utils.RestErrWithLog(
1✔
767
                        w,
1✔
768
                        r,
1✔
769
                        l,
1✔
770
                        errors.New("device id (did) cannot be empty"),
1✔
771
                        http.StatusBadRequest,
1✔
772
                )
1✔
773
                return
1✔
774
        }
1✔
775

776
        status, err := d.devAuth.GetTenantDeviceStatus(ctx, tid, did)
1✔
777
        switch err {
1✔
778
        case nil:
1✔
779
                _ = w.WriteJson(status)
1✔
780
        case devauth.ErrDeviceNotFound:
1✔
781
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
1✔
782
        default:
1✔
783
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
784
        }
785
}
786

787
func (d *DevAuthApiHandlers) GetTenantDevicesHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
788
        ctx := r.Context()
1✔
789
        if tid := r.PathParam("tid"); tid != "" {
2✔
790
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
1✔
791
        }
1✔
792
        r.Request = r.WithContext(ctx)
1✔
793

1✔
794
        d.GetDevicesV2Handler(w, r)
1✔
795
}
796

797
func (d *DevAuthApiHandlers) GetTenantDevicesCountHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
798
        ctx := r.Context()
1✔
799
        if tid := r.PathParam("tid"); tid != "" {
2✔
800
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
1✔
801
        }
1✔
802
        r.Request = r.WithContext(ctx)
1✔
803

1✔
804
        d.GetDevicesCountHandler(w, r)
1✔
805
}
806

UNCOV
807
func (d *DevAuthApiHandlers) DeleteDeviceHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
808
        ctx := r.Context()
×
UNCOV
809
        l := log.FromContext(ctx)
×
UNCOV
810
        did := r.PathParam("did")
×
UNCOV
811

×
UNCOV
812
        err := d.devAuth.DeleteDevice(ctx, did)
×
UNCOV
813
        switch err {
×
UNCOV
814
        case nil:
×
UNCOV
815
                w.WriteHeader(http.StatusNoContent)
×
816
        case devauth.ErrInvalidDeviceID:
×
817
                didErr := errors.New("device id (did) cannot be empty")
×
818
                rest_utils.RestErrWithLog(w, r, l, didErr, http.StatusBadRequest)
×
UNCOV
819
        case store.ErrDevNotFound:
×
UNCOV
820
                w.WriteHeader(http.StatusNotFound)
×
821
        default:
×
822
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
823
        }
824
}
825

826
// Validate status.
827
// Expected statuses:
828
// - "accepted"
829
// - "rejected"
830
// - "pending"
831
func statusValidate(status *DevAuthApiStatus) error {
1✔
832
        if status.Status != model.DevStatusAccepted &&
1✔
833
                status.Status != model.DevStatusRejected &&
1✔
834
                status.Status != model.DevStatusPending {
2✔
835
                return ErrIncorrectStatus
1✔
836
        } else {
2✔
837
                return nil
1✔
838
        }
1✔
839
}
840

841
// extracts JWT from authorization header
842
func extractToken(header http.Header) (string, error) {
1✔
843
        const authHeaderName = "Authorization"
1✔
844
        authHeader := header.Get(authHeaderName)
1✔
845
        if authHeader == "" {
2✔
846
                return "", ErrNoAuthHeader
1✔
847
        }
1✔
848
        tokenStr := strings.Replace(authHeader, "Bearer", "", 1)
1✔
849
        tokenStr = strings.Replace(tokenStr, "bearer", "", 1)
1✔
850
        return strings.TrimSpace(tokenStr), nil
1✔
851
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc