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

mendersoftware / deviceauth / 728240484

pending completion
728240484

push

gitlab-ci

GitHub
Merge pull request #622 from 0lmi/me-41_device_decommissioning_inconsistency

14 of 21 new or added lines in 2 files covered. (66.67%)

84 existing lines in 2 files now uncovered.

4403 of 5219 relevant lines covered (84.36%)

49.02 hits per line

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

92.64
/api/http/api_devauth.go
1
// Copyright 2022 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/mendersoftware/go-lib-micro/identity"
24

25
        "github.com/ant0ine/go-json-rest/rest"
26
        ctxhttpheader "github.com/mendersoftware/go-lib-micro/context/httpheader"
27
        "github.com/mendersoftware/go-lib-micro/log"
28
        "github.com/mendersoftware/go-lib-micro/rest_utils"
29
        "github.com/pkg/errors"
30

31
        "github.com/mendersoftware/deviceauth/access"
32
        "github.com/mendersoftware/deviceauth/cache"
33
        "github.com/mendersoftware/deviceauth/devauth"
34
        "github.com/mendersoftware/deviceauth/jwt"
35
        "github.com/mendersoftware/deviceauth/model"
36
        "github.com/mendersoftware/deviceauth/store"
37
        "github.com/mendersoftware/deviceauth/utils"
38
)
39

40
const (
41
        uriAuthReqs = "/api/devices/v1/authentication/auth_requests"
42

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

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

65
        HdrAuthReqSign = "X-MEN-Signature"
66
)
67

68
const (
69
        defaultTimeout = time.Second * 5
70
)
71

72
var (
73
        ErrIncorrectStatus = errors.New("incorrect device status")
74
        ErrNoAuthHeader    = errors.New("no authorization header")
75
)
76

77
type DevAuthApiHandlers struct {
78
        devAuth devauth.App
79
        db      store.DataStore
80
}
81

82
type DevAuthApiStatus struct {
83
        Status string `json:"status"`
84
}
85

86
func NewDevAuthApiHandlers(devAuth devauth.App, db store.DataStore) ApiHandler {
197✔
87
        return &DevAuthApiHandlers{
197✔
88
                devAuth: devAuth,
197✔
89
                db:      db,
197✔
90
        }
197✔
91
}
197✔
92

93
func (d *DevAuthApiHandlers) GetApp() (rest.App, error) {
197✔
94
        routes := []*rest.Route{
197✔
95
                rest.Get(uriAlive, d.AliveHandler),
197✔
96
                rest.Get(uriHealth, d.HealthCheckHandler),
197✔
97
                rest.Post(uriAuthReqs, d.SubmitAuthRequestHandler),
197✔
98
                rest.Get(uriTokenVerify, d.VerifyTokenHandler),
197✔
99
                rest.Post(uriTokenVerify, d.VerifyTokenHandler),
197✔
100
                rest.Delete(uriTokens, d.DeleteTokensHandler),
197✔
101

197✔
102
                rest.Put(uriTenantLimit, d.PutTenantLimitHandler),
197✔
103
                rest.Get(uriTenantLimit, d.GetTenantLimitHandler),
197✔
104
                rest.Delete(uriTenantLimit, d.DeleteTenantLimitHandler),
197✔
105

197✔
106
                rest.Post(uriTenants, d.ProvisionTenantHandler),
197✔
107
                rest.Get(uriTenantDeviceStatus, d.GetTenantDeviceStatus),
197✔
108
                rest.Get(uriTenantDevices, d.GetTenantDevicesHandler),
197✔
109
                rest.Get(uriTenantDevicesCount, d.GetTenantDevicesCountHandler),
197✔
110
                rest.Delete(uriTenantDevice, d.DeleteDeviceHandler),
197✔
111

197✔
112
                // API v2
197✔
113
                rest.Get(v2uriDevicesCount, d.GetDevicesCountHandler),
197✔
114
                rest.Get(v2uriDevices, d.GetDevicesV2Handler),
197✔
115
                rest.Post(v2uriDevicesSearch, d.SearchDevicesV2Handler),
197✔
116
                rest.Post(v2uriDevices, d.PostDevicesV2Handler),
197✔
117
                rest.Get(v2uriDevice, d.GetDeviceV2Handler),
197✔
118
                rest.Delete(v2uriDevice, d.DecommissionDeviceHandler),
197✔
119
                rest.Delete(v2uriDeviceAuthSet, d.DeleteDeviceAuthSetHandler),
197✔
120
                rest.Put(v2uriDeviceAuthSetStatus, d.UpdateDeviceStatusHandler),
197✔
121
                rest.Get(v2uriDeviceAuthSetStatus, d.GetAuthSetStatusHandler),
197✔
122
                rest.Delete(v2uriToken, d.DeleteTokenHandler),
197✔
123
                rest.Get(v2uriDevicesLimit, d.GetLimitHandler),
197✔
124
        }
197✔
125

197✔
126
        app, err := rest.MakeRouter(
197✔
127
                // augment routes with OPTIONS handler
197✔
128
                AutogenOptionsRoutes(routes, AllowHeaderOptionsGenerator)...,
197✔
129
        )
197✔
130
        if err != nil {
197✔
UNCOV
131
                return nil, errors.Wrap(err, "failed to create router")
×
UNCOV
132
        }
×
133

134
        return app, nil
197✔
135
}
136

137
func (d *DevAuthApiHandlers) AliveHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
138
        w.WriteHeader(http.StatusNoContent)
2✔
139
}
2✔
140

141
func (d *DevAuthApiHandlers) HealthCheckHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
142
        ctx := r.Context()
4✔
143
        l := log.FromContext(ctx)
4✔
144
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
4✔
145
        defer cancel()
4✔
146

4✔
147
        err := d.devAuth.HealthCheck(ctx)
4✔
148
        if err != nil {
6✔
149
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusServiceUnavailable)
2✔
150
                return
2✔
151
        }
2✔
152
        w.WriteHeader(http.StatusNoContent)
2✔
153
}
154

155
func (d *DevAuthApiHandlers) SubmitAuthRequestHandler(w rest.ResponseWriter, r *rest.Request) {
21✔
156
        var authreq model.AuthReq
21✔
157

21✔
158
        ctx := r.Context()
21✔
159

21✔
160
        l := log.FromContext(ctx)
21✔
161

21✔
162
        //validate req body by reading raw content manually
21✔
163
        //(raw body will be needed later, DecodeJsonPayload would
21✔
164
        //unmarshal and close it)
21✔
165
        body, err := utils.ReadBodyRaw(r)
21✔
166
        if err != nil {
21✔
UNCOV
167
                err = errors.Wrap(err, "failed to decode auth request")
×
UNCOV
168
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
×
169
                return
×
170
        }
×
171

172
        err = json.Unmarshal(body, &authreq)
21✔
173
        if err != nil {
23✔
174
                err = errors.Wrap(err, "failed to decode auth request")
2✔
175
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
176
                return
2✔
177
        }
2✔
178

179
        err = authreq.Validate()
19✔
180
        if err != nil {
28✔
181
                err = errors.Wrap(err, "invalid auth request")
9✔
182
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
9✔
183
                return
9✔
184
        }
9✔
185

186
        //verify signature
187
        signature := r.Header.Get(HdrAuthReqSign)
11✔
188
        if signature == "" {
13✔
189
                rest_utils.RestErrWithLog(
2✔
190
                        w,
2✔
191
                        r,
2✔
192
                        l,
2✔
193
                        errors.New("missing request signature header"),
2✔
194
                        http.StatusBadRequest,
2✔
195
                )
2✔
196
                return
2✔
197
        }
2✔
198

199
        err = utils.VerifyAuthReqSign(signature, authreq.PubKeyStruct, body)
9✔
200
        if err != nil {
11✔
201
                rest_utils.RestErrWithLogMsg(
2✔
202
                        w,
2✔
203
                        r,
2✔
204
                        l,
2✔
205
                        err,
2✔
206
                        http.StatusUnauthorized,
2✔
207
                        "signature verification failed",
2✔
208
                )
2✔
209
                return
2✔
210
        }
2✔
211

212
        token, err := d.devAuth.SubmitAuthRequest(ctx, &authreq)
7✔
213

7✔
214
        if err != nil {
10✔
215
                if devauth.IsErrDevAuthUnauthorized(err) {
6✔
216
                        rest_utils.RestErrWithWarningMsg(w, r, l, err,
3✔
217
                                http.StatusUnauthorized, errors.Cause(err).Error())
3✔
218
                        return
3✔
219
                } else if devauth.IsErrDevAuthBadRequest(err) {
4✔
UNCOV
220
                        rest_utils.RestErrWithWarningMsg(w, r, l, err,
×
UNCOV
221
                                http.StatusBadRequest, errors.Cause(err).Error())
×
222
                        return
×
223
                }
×
224
        }
225

226
        switch err {
5✔
UNCOV
227
        case devauth.ErrDevIdAuthIdMismatch, devauth.ErrMaxDeviceCountReached:
×
UNCOV
228
                // error is always set to unauthorized, client does not need to
×
229
                // know why
×
230
                rest_utils.RestErrWithWarningMsg(w, r, l, devauth.ErrDevAuthUnauthorized,
×
231
                        http.StatusUnauthorized, "unauthorized")
×
232
                return
×
233
        case nil:
5✔
234
                _, _ = w.(http.ResponseWriter).Write([]byte(token))
5✔
235
                w.Header().Set("Content-Type", "application/jwt")
5✔
236
                return
5✔
237
        default:
1✔
238
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
239
                return
1✔
240
        }
241
}
242

243
func (d *DevAuthApiHandlers) PostDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
18✔
244
        ctx := r.Context()
18✔
245

18✔
246
        l := log.FromContext(ctx)
18✔
247

18✔
248
        req, err := parsePreAuthReq(r.Body)
18✔
249
        if err != nil {
28✔
250
                err = errors.Wrap(err, "failed to decode preauth request")
10✔
251
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
10✔
252
                return
10✔
253
        }
10✔
254

255
        reqDbModel, err := req.getDbModel()
8✔
256
        if err != nil {
8✔
UNCOV
257
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
258
                return
×
259
        }
×
260

261
        device, err := d.devAuth.PreauthorizeDevice(ctx, reqDbModel)
8✔
262
        switch err {
8✔
263
        case nil:
4✔
264
                w.Header().Set("Location", "devices/"+reqDbModel.DeviceId)
4✔
265
                w.WriteHeader(http.StatusCreated)
4✔
266
        case devauth.ErrDeviceExists:
2✔
267
                l.Error(err)
2✔
268
                w.WriteHeader(http.StatusConflict)
2✔
269
                _ = w.WriteJson(device)
2✔
270
        default:
2✔
271
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
272
        }
273
}
274

275
func (d *DevAuthApiHandlers) SearchDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
35✔
276
        ctx := r.Context()
35✔
277
        l := log.FromContext(ctx)
35✔
278

35✔
279
        page, perPage, err := rest_utils.ParsePagination(r)
35✔
280
        if err != nil {
37✔
281
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
282
                return
2✔
283
        }
2✔
284
        fltr := model.DeviceFilter{}
33✔
285

33✔
286
        switch strings.ToLower(r.Header.Get("Content-Type")) {
33✔
287
        case "application/json", "":
4✔
288
                err := r.DecodeJsonPayload(&fltr)
4✔
289
                if err != nil {
6✔
290
                        err = errors.Wrap(err, "api: malformed request body")
2✔
291
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
292
                        return
2✔
293
                }
2✔
294

295
        case "application/x-www-form-urlencoded":
27✔
296
                if err = r.ParseForm(); err != nil {
29✔
297
                        err = errors.Wrap(err, "api: malformed query parameters")
2✔
298
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
299
                        return
2✔
300
                }
2✔
301
                if err = fltr.ParseForm(r.Form); err != nil {
27✔
302
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
303
                        return
2✔
304
                }
2✔
305

306
        default:
2✔
307
                rest_utils.RestErrWithLog(w, r, l, errors.Errorf(
2✔
308
                        "Content-Type '%s' not supported",
2✔
309
                        r.Header.Get("Content-Type"),
2✔
310
                ), http.StatusUnsupportedMediaType)
2✔
311
                return
2✔
312
        }
313

314
        skip := (page - 1) * perPage
25✔
315
        limit := perPage + 1
25✔
316
        devs, err := d.devAuth.GetDevices(ctx, uint(skip), uint(limit), fltr)
25✔
317
        if err != nil {
29✔
318
                rest_utils.RestErrWithLogInternal(w, r, l, err)
4✔
319
                return
4✔
320
        }
4✔
321

322
        numDevs := len(devs)
21✔
323
        hasNext := false
21✔
324
        if uint64(numDevs) > perPage {
26✔
325
                hasNext = true
5✔
326
                numDevs = int(perPage)
5✔
327
        }
5✔
328

329
        links := rest_utils.MakePageLinkHdrs(r, page, perPage, hasNext)
21✔
330

21✔
331
        for _, l := range links {
50✔
332
                w.Header().Add("Link", l)
29✔
333
        }
29✔
334

335
        _ = w.WriteJson(devs[:numDevs])
21✔
336
}
337

338
func (d *DevAuthApiHandlers) GetDevicesV2Handler(w rest.ResponseWriter, r *rest.Request) {
21✔
339
        r.Header.Set("Content-Type", "application/x-www-form-urlencoded")
21✔
340
        d.SearchDevicesV2Handler(w, r)
21✔
341
}
21✔
342

343
func (d *DevAuthApiHandlers) GetDevicesCountHandler(w rest.ResponseWriter, r *rest.Request) {
25✔
344
        ctx := r.Context()
25✔
345
        l := log.FromContext(ctx)
25✔
346

25✔
347
        status := r.URL.Query().Get("status")
25✔
348

25✔
349
        switch status {
25✔
350
        case model.DevStatusAccepted,
351
                model.DevStatusRejected,
352
                model.DevStatusPending,
353
                model.DevStatusPreauth,
354
                model.DevStatusNoAuth,
355
                "":
23✔
356
        default:
2✔
357
                rest_utils.RestErrWithLog(
2✔
358
                        w,
2✔
359
                        r,
2✔
360
                        l,
2✔
361
                        errors.New("status must be one of: pending, accepted, rejected, preauthorized, noauth"),
2✔
362
                        http.StatusBadRequest,
2✔
363
                )
2✔
364
                return
2✔
365
        }
366

367
        count, err := d.devAuth.GetDevCountByStatus(ctx, status)
23✔
368

23✔
369
        if err != nil {
27✔
370
                rest_utils.RestErrWithLogInternal(w, r, l, err)
4✔
371
                return
4✔
372
        }
4✔
373

374
        _ = w.WriteJson(model.Count{Count: count})
19✔
375
}
376

377
func (d *DevAuthApiHandlers) GetDeviceV2Handler(w rest.ResponseWriter, r *rest.Request) {
7✔
378

7✔
379
        ctx := r.Context()
7✔
380

7✔
381
        l := log.FromContext(ctx)
7✔
382

7✔
383
        devId := r.PathParam("id")
7✔
384

7✔
385
        dev, err := d.devAuth.GetDevice(ctx, devId)
7✔
386
        switch {
7✔
387
        case err == store.ErrDevNotFound:
3✔
388
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
3✔
389
        case dev != nil:
3✔
390
                _ = w.WriteJson(dev)
3✔
391
        default:
2✔
392
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
393
        }
394
}
395

396
func (d *DevAuthApiHandlers) DecommissionDeviceHandler(w rest.ResponseWriter, r *rest.Request) {
7✔
397

7✔
398
        ctx := r.Context()
7✔
399

7✔
400
        l := log.FromContext(ctx)
7✔
401

7✔
402
        devId := r.PathParam("id")
7✔
403

7✔
404
        if err := d.devAuth.DecommissionDevice(ctx, devId); err != nil {
12✔
405
                if err == store.ErrDevNotFound {
8✔
406
                        w.WriteHeader(http.StatusNotFound)
3✔
407
                        return
3✔
408
                }
3✔
409
                rest_utils.RestErrWithLogInternal(w, r, l, err)
3✔
410
                return
3✔
411
        }
412

413
        w.WriteHeader(http.StatusNoContent)
3✔
414
}
415

416
func (d *DevAuthApiHandlers) DeleteDeviceAuthSetHandler(w rest.ResponseWriter, r *rest.Request) {
7✔
417

7✔
418
        ctx := r.Context()
7✔
419

7✔
420
        l := log.FromContext(ctx)
7✔
421

7✔
422
        devId := r.PathParam("id")
7✔
423
        authId := r.PathParam("aid")
7✔
424

7✔
425
        if err := d.devAuth.DeleteAuthSet(ctx, devId, authId); err != nil {
12✔
426
                if err == store.ErrAuthSetNotFound {
8✔
427
                        w.WriteHeader(http.StatusNotFound)
3✔
428
                        return
3✔
429
                }
3✔
430
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
431
                return
2✔
432
        }
433

434
        w.WriteHeader(http.StatusNoContent)
3✔
435
}
436

437
func (d *DevAuthApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
7✔
438
        ctx := r.Context()
7✔
439

7✔
440
        l := log.FromContext(ctx)
7✔
441

7✔
442
        tokenID := r.PathParam("id")
7✔
443
        err := d.devAuth.RevokeToken(ctx, tokenID)
7✔
444
        if err != nil {
11✔
445
                if err == store.ErrTokenNotFound ||
4✔
446
                        err == devauth.ErrInvalidAuthSetID {
6✔
447
                        w.WriteHeader(http.StatusNotFound)
2✔
448
                        return
2✔
449
                }
2✔
450
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
451
                return
2✔
452
        }
453

454
        w.WriteHeader(http.StatusNoContent)
3✔
455
}
456

457
func (d *DevAuthApiHandlers) VerifyTokenHandler(w rest.ResponseWriter, r *rest.Request) {
17✔
458
        ctx := r.Context()
17✔
459

17✔
460
        l := log.FromContext(ctx)
17✔
461

17✔
462
        tokenStr, err := extractToken(r.Header)
17✔
463
        if err != nil {
20✔
464
                rest_utils.RestErrWithLog(w, r, l, ErrNoAuthHeader, http.StatusUnauthorized)
3✔
465
                return
3✔
466
        }
3✔
467

468
        ctx = ctxhttpheader.WithContext(ctx,
15✔
469
                r.Header,
15✔
470
                "X-Forwarded-Method",
15✔
471
                "X-Forwarded-Uri")
15✔
472

15✔
473
        // verify token
15✔
474
        err = d.devAuth.VerifyToken(ctx, tokenStr)
15✔
475
        code := http.StatusOK
15✔
476
        if err != nil {
26✔
477
                switch e := errors.Cause(err); e {
11✔
478
                case jwt.ErrTokenExpired:
2✔
479
                        code = http.StatusForbidden
2✔
480
                case store.ErrTokenNotFound, store.ErrAuthSetNotFound, jwt.ErrTokenInvalid:
5✔
481
                        code = http.StatusUnauthorized
5✔
482
                case cache.ErrTooManyRequests:
2✔
483
                        code = http.StatusTooManyRequests
2✔
484
                default:
3✔
485
                        if _, ok := e.(access.PermissionError); ok {
4✔
486
                                rest_utils.RestErrWithLog(w, r, l, e, http.StatusForbidden)
1✔
487
                        } else {
3✔
488
                                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
489
                        }
2✔
490
                        return
3✔
491
                }
492
                l.Error(err)
9✔
493
        }
494

495
        w.WriteHeader(code)
13✔
496
}
497

498
func (d *DevAuthApiHandlers) UpdateDeviceStatusHandler(w rest.ResponseWriter, r *rest.Request) {
19✔
499
        ctx := r.Context()
19✔
500

19✔
501
        l := log.FromContext(ctx)
19✔
502

19✔
503
        devid := r.PathParam("id")
19✔
504
        authid := r.PathParam("aid")
19✔
505

19✔
506
        var status DevAuthApiStatus
19✔
507
        err := r.DecodeJsonPayload(&status)
19✔
508
        if err != nil {
21✔
509
                err = errors.Wrap(err, "failed to decode status data")
2✔
510
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
511
                return
2✔
512
        }
2✔
513

514
        if err := statusValidate(&status); err != nil {
19✔
515
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
516
                return
2✔
517
        }
2✔
518

519
        if status.Status == model.DevStatusAccepted {
24✔
520
                err = d.devAuth.AcceptDeviceAuth(ctx, devid, authid)
9✔
521
        } else if status.Status == model.DevStatusRejected {
19✔
522
                err = d.devAuth.RejectDeviceAuth(ctx, devid, authid)
3✔
523
        } else if status.Status == model.DevStatusPending {
13✔
524
                err = d.devAuth.ResetDeviceAuth(ctx, devid, authid)
5✔
525
        }
5✔
526
        if err != nil {
24✔
527
                switch err {
9✔
528
                case store.ErrDevNotFound, store.ErrAuthSetNotFound:
3✔
529
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
3✔
530
                case devauth.ErrDevIdAuthIdMismatch, devauth.ErrDevAuthBadRequest:
2✔
531
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
532
                case devauth.ErrMaxDeviceCountReached:
3✔
533
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
3✔
534
                default:
3✔
535
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
3✔
536
                }
537
                return
9✔
538
        }
539

540
        w.WriteHeader(http.StatusNoContent)
7✔
541
}
542

543
type LimitValue struct {
544
        Limit uint64 `json:"limit"`
545
}
546

547
func (d *DevAuthApiHandlers) PutTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
9✔
548
        ctx := r.Context()
9✔
549

9✔
550
        l := log.FromContext(ctx)
9✔
551

9✔
552
        tenantId := r.PathParam("id")
9✔
553
        reqLimitName := r.PathParam("name")
9✔
554

9✔
555
        if !model.IsValidLimit(reqLimitName) {
11✔
556
                rest_utils.RestErrWithLog(w, r, l,
2✔
557
                        errors.Errorf("unsupported limit %v", reqLimitName),
2✔
558
                        http.StatusBadRequest)
2✔
559
                return
2✔
560
        }
2✔
561

562
        var value LimitValue
7✔
563
        err := r.DecodeJsonPayload(&value)
7✔
564
        if err != nil {
10✔
565
                err = errors.Wrap(err, "failed to decode limit request")
3✔
566
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
3✔
567
                return
3✔
568
        }
3✔
569

570
        limit := model.Limit{
5✔
571
                Value: value.Limit,
5✔
572
                Name:  reqLimitName,
5✔
573
        }
5✔
574

5✔
575
        if err := d.devAuth.SetTenantLimit(ctx, tenantId, limit); err != nil {
7✔
576
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
577
                return
2✔
578
        }
2✔
579

580
        w.WriteHeader(http.StatusNoContent)
3✔
581
}
582

583
func (d *DevAuthApiHandlers) DeleteTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
6✔
584
        ctx := r.Context()
6✔
585

6✔
586
        l := log.FromContext(ctx)
6✔
587

6✔
588
        tenantId := r.PathParam("id")
6✔
589
        reqLimitName := r.PathParam("name")
6✔
590

6✔
591
        if !model.IsValidLimit(reqLimitName) {
8✔
592
                rest_utils.RestErrWithLog(w, r, l,
2✔
593
                        errors.Errorf("unsupported limit %v", reqLimitName),
2✔
594
                        http.StatusBadRequest)
2✔
595
                return
2✔
596
        }
2✔
597

598
        if err := d.devAuth.DeleteTenantLimit(ctx, tenantId, reqLimitName); err != nil {
6✔
599
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
600
                return
2✔
601
        }
2✔
602

603
        w.WriteHeader(http.StatusNoContent)
2✔
604
}
605

606
func (d *DevAuthApiHandlers) GetTenantLimitHandler(w rest.ResponseWriter, r *rest.Request) {
7✔
607
        ctx := r.Context()
7✔
608

7✔
609
        l := log.FromContext(ctx)
7✔
610

7✔
611
        tenantId := r.PathParam("id")
7✔
612
        limitName := r.PathParam("name")
7✔
613

7✔
614
        if !model.IsValidLimit(limitName) {
9✔
615
                rest_utils.RestErrWithLog(w, r, l,
2✔
616
                        errors.Errorf("unsupported limit %v", limitName),
2✔
617
                        http.StatusBadRequest)
2✔
618
                return
2✔
619
        }
2✔
620

621
        lim, err := d.devAuth.GetTenantLimit(ctx, limitName, tenantId)
5✔
622
        if err != nil {
7✔
623
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
624
                return
2✔
625
        }
2✔
626

627
        _ = w.WriteJson(LimitValue{lim.Value})
3✔
628
}
629

630
func (d *DevAuthApiHandlers) GetLimitHandler(w rest.ResponseWriter, r *rest.Request) {
7✔
631
        ctx := r.Context()
7✔
632

7✔
633
        l := log.FromContext(ctx)
7✔
634

7✔
635
        name := r.PathParam("name")
7✔
636

7✔
637
        if !model.IsValidLimit(name) {
9✔
638
                rest_utils.RestErrWithLog(w, r, l,
2✔
639
                        errors.Errorf("unsupported limit %v", name),
2✔
640
                        http.StatusBadRequest)
2✔
641
                return
2✔
642
        }
2✔
643

644
        lim, err := d.devAuth.GetLimit(ctx, name)
5✔
645
        if err != nil {
7✔
646
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
647
                return
2✔
648
        }
2✔
649

650
        _ = w.WriteJson(LimitValue{lim.Value})
3✔
651
}
652

653
func (d *DevAuthApiHandlers) DeleteTokensHandler(w rest.ResponseWriter, r *rest.Request) {
9✔
654

9✔
655
        ctx := r.Context()
9✔
656

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

9✔
659
        tenantId := r.URL.Query().Get("tenant_id")
9✔
660
        if tenantId == "" {
12✔
661
                rest_utils.RestErrWithLog(
3✔
662
                        w,
3✔
663
                        r,
3✔
664
                        l,
3✔
665
                        errors.New("tenant_id must be provided"),
3✔
666
                        http.StatusBadRequest,
3✔
667
                )
3✔
668
                return
3✔
669
        }
3✔
670
        devId := r.URL.Query().Get("device_id")
7✔
671

7✔
672
        err := d.devAuth.DeleteTokens(ctx, tenantId, devId)
7✔
673
        switch err {
7✔
674
        case nil:
5✔
675
                w.WriteHeader(http.StatusNoContent)
5✔
676
        default:
2✔
677
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
678
        }
679
}
680

UNCOV
681
func (d *DevAuthApiHandlers) GetAuthSetStatusHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
682
        ctx := r.Context()
×
683
        l := log.FromContext(ctx)
×
684

×
685
        devid := r.PathParam("id")
×
686
        authid := r.PathParam("aid")
×
687

×
688
        // get authset directly from store
×
689
        aset, err := d.db.GetAuthSetById(ctx, authid)
×
690
        switch err {
×
691
        case nil:
×
692
                _ = w.WriteJson(&model.Status{Status: aset.Status})
×
693
        case store.ErrDevNotFound, store.ErrAuthSetNotFound:
×
694
                rest_utils.RestErrWithLog(w, r, l, store.ErrAuthSetNotFound, http.StatusNotFound)
×
695
        default:
×
696
                rest_utils.RestErrWithLogInternal(w, r, l,
×
697
                        errors.Wrapf(err,
×
698
                                "failed to fetch auth set %s for device %s",
×
699
                                authid, devid))
×
700
        }
701
}
702

703
func (d *DevAuthApiHandlers) ProvisionTenantHandler(w rest.ResponseWriter, r *rest.Request) {
9✔
704
        ctx := r.Context()
9✔
705
        l := log.FromContext(ctx)
9✔
706

9✔
707
        defer r.Body.Close()
9✔
708

9✔
709
        tenant, err := model.ParseNewTenant(r.Body)
9✔
710
        if err != nil {
14✔
711
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
5✔
712
                return
5✔
713
        }
5✔
714

715
        err = d.devAuth.ProvisionTenant(ctx, tenant.TenantId)
5✔
716
        if err != nil {
7✔
717
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
718
                return
2✔
719
        }
2✔
720

721
        w.WriteHeader(http.StatusCreated)
3✔
722
}
723

724
func (d *DevAuthApiHandlers) GetTenantDeviceStatus(w rest.ResponseWriter, r *rest.Request) {
10✔
725
        ctx := r.Context()
10✔
726

10✔
727
        l := log.FromContext(ctx)
10✔
728

10✔
729
        tid := r.PathParam("tid")
10✔
730
        did := r.PathParam("did")
10✔
731

10✔
732
        if did == "" {
12✔
733
                rest_utils.RestErrWithLog(
2✔
734
                        w,
2✔
735
                        r,
2✔
736
                        l,
2✔
737
                        errors.New("device id (did) cannot be empty"),
2✔
738
                        http.StatusBadRequest,
2✔
739
                )
2✔
740
                return
2✔
741
        }
2✔
742

743
        status, err := d.devAuth.GetTenantDeviceStatus(ctx, tid, did)
8✔
744
        switch err {
8✔
745
        case nil:
4✔
746
                _ = w.WriteJson(status)
4✔
747
        case devauth.ErrDeviceNotFound:
2✔
748
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
2✔
749
        default:
2✔
750
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
751
        }
752
}
753

754
func (d *DevAuthApiHandlers) GetTenantDevicesHandler(w rest.ResponseWriter, r *rest.Request) {
12✔
755
        ctx := r.Context()
12✔
756
        if tid := r.PathParam("tid"); tid != "" {
22✔
757
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
10✔
758
        }
10✔
759
        r.Request = r.WithContext(ctx)
12✔
760

12✔
761
        d.GetDevicesV2Handler(w, r)
12✔
762
}
763

764
func (d *DevAuthApiHandlers) GetTenantDevicesCountHandler(w rest.ResponseWriter, r *rest.Request) {
8✔
765
        ctx := r.Context()
8✔
766
        if tid := r.PathParam("tid"); tid != "" {
14✔
767
                ctx = identity.WithContext(ctx, &identity.Identity{Tenant: tid})
6✔
768
        }
6✔
769
        r.Request = r.WithContext(ctx)
8✔
770

8✔
771
        d.GetDevicesCountHandler(w, r)
8✔
772
}
773

774
func (d *DevAuthApiHandlers) DeleteDeviceHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
775
        ctx := r.Context()
1✔
776
        l := log.FromContext(ctx)
1✔
777
        did := r.PathParam("did")
1✔
778

1✔
779
        err := d.devAuth.DeleteDevice(ctx, did)
1✔
780
        switch err {
1✔
781
        case nil:
1✔
782
                w.WriteHeader(http.StatusNoContent)
1✔
NEW
783
        case devauth.ErrInvalidDeviceID:
×
NEW
784
                didErr := errors.New("device id (did) cannot be empty")
×
NEW
785
                rest_utils.RestErrWithLog(w, r, l, didErr, http.StatusBadRequest)
×
NEW
786
        case store.ErrDevNotFound:
×
NEW
787
                w.WriteHeader(http.StatusNotFound)
×
NEW
788
        default:
×
NEW
789
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
790
        }
791
}
792

793
// Validate status.
794
// Expected statuses:
795
// - "accepted"
796
// - "rejected"
797
// - "pending"
798
func statusValidate(status *DevAuthApiStatus) error {
17✔
799
        if status.Status != model.DevStatusAccepted &&
17✔
800
                status.Status != model.DevStatusRejected &&
17✔
801
                status.Status != model.DevStatusPending {
19✔
802
                return ErrIncorrectStatus
2✔
803
        } else {
17✔
804
                return nil
15✔
805
        }
15✔
806
}
807

808
// extracts JWT from authorization header
809
func extractToken(header http.Header) (string, error) {
17✔
810
        const authHeaderName = "Authorization"
17✔
811
        authHeader := header.Get(authHeaderName)
17✔
812
        if authHeader == "" {
20✔
813
                return "", ErrNoAuthHeader
3✔
814
        }
3✔
815
        tokenStr := strings.Replace(authHeader, "Bearer", "", 1)
15✔
816
        tokenStr = strings.Replace(tokenStr, "bearer", "", 1)
15✔
817
        return strings.TrimSpace(tokenStr), nil
15✔
818
}
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