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

mendersoftware / deviceauth / 865578666

pending completion
865578666

Pull #645

gitlab-ci

Peter Grzybowski
chore: update last check-in on successful auth
Pull Request #645: chore: update last check-in on successful auth

1 of 1 new or added line in 1 file covered. (100.0%)

7 existing lines in 1 file now uncovered.

4659 of 5541 relevant lines covered (84.08%)

46.02 hits per line

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

89.34
/devauth/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 devauth
15

16
import (
17
        "context"
18
        "crypto/sha256"
19
        "encoding/json"
20
        "fmt"
21
        "net/http"
22
        "strings"
23
        "time"
24

25
        "github.com/mendersoftware/go-lib-micro/addons"
26
        "github.com/mendersoftware/go-lib-micro/apiclient"
27
        ctxhttpheader "github.com/mendersoftware/go-lib-micro/context/httpheader"
28
        "github.com/mendersoftware/go-lib-micro/identity"
29
        "github.com/mendersoftware/go-lib-micro/log"
30
        "github.com/mendersoftware/go-lib-micro/mongo/oid"
31
        "github.com/mendersoftware/go-lib-micro/plan"
32
        "github.com/mendersoftware/go-lib-micro/ratelimits"
33
        "github.com/mendersoftware/go-lib-micro/requestid"
34
        mstore "github.com/mendersoftware/go-lib-micro/store"
35
        "github.com/pkg/errors"
36
        "go.mongodb.org/mongo-driver/bson"
37

38
        "github.com/mendersoftware/deviceauth/access"
39
        "github.com/mendersoftware/deviceauth/cache"
40
        "github.com/mendersoftware/deviceauth/client/inventory"
41
        "github.com/mendersoftware/deviceauth/client/orchestrator"
42
        "github.com/mendersoftware/deviceauth/client/tenant"
43
        "github.com/mendersoftware/deviceauth/jwt"
44
        "github.com/mendersoftware/deviceauth/model"
45
        "github.com/mendersoftware/deviceauth/store"
46
        "github.com/mendersoftware/deviceauth/store/mongo"
47
        "github.com/mendersoftware/deviceauth/utils"
48
        uto "github.com/mendersoftware/deviceauth/utils/to"
49
)
50

51
const (
52
        MsgErrDevAuthUnauthorized = "dev auth: unauthorized"
53
        MsgErrDevAuthBadRequest   = "dev auth: bad request"
54
)
55

56
var (
57
        ErrDevAuthUnauthorized   = errors.New(MsgErrDevAuthUnauthorized)
58
        ErrDevIdAuthIdMismatch   = errors.New("dev auth: dev ID and auth ID mismatch")
59
        ErrMaxDeviceCountReached = errors.New("maximum number of accepted devices reached")
60
        ErrDeviceExists          = errors.New("device already exists")
61
        ErrDeviceNotFound        = errors.New("device not found")
62
        ErrDevAuthBadRequest     = errors.New(MsgErrDevAuthBadRequest)
63

64
        ErrInvalidDeviceID  = errors.New("invalid device ID type")
65
        ErrInvalidAuthSetID = errors.New("auth set id is not a valid ID")
66
)
67

68
func IsErrDevAuthUnauthorized(e error) bool {
1✔
69
        return strings.HasPrefix(e.Error(), MsgErrDevAuthUnauthorized)
1✔
70
}
1✔
71

72
func MakeErrDevAuthUnauthorized(e error) error {
13✔
73
        return errors.Wrap(e, MsgErrDevAuthUnauthorized)
13✔
74
}
13✔
75

76
func IsErrDevAuthBadRequest(e error) bool {
1✔
77
        return strings.HasPrefix(e.Error(), MsgErrDevAuthBadRequest)
1✔
78
}
1✔
79

80
func MakeErrDevAuthBadRequest(e error) error {
4✔
81
        return errors.Wrap(e, MsgErrDevAuthBadRequest)
4✔
82
}
4✔
83

84
// helper for obtaining API clients
85
type ApiClientGetter func() apiclient.HttpRunner
86

87
func simpleApiClientGetter() apiclient.HttpRunner {
×
88
        return &apiclient.HttpApi{}
×
89
}
×
90

91
// this device auth service interface
92
//
93
//go:generate ../utils/mockgen.sh
94
type App interface {
95
        HealthCheck(ctx context.Context) error
96
        SubmitAuthRequest(ctx context.Context, r *model.AuthReq) (string, error)
97

98
        GetDevices(
99
                ctx context.Context,
100
                skip,
101
                limit uint,
102
                filter model.DeviceFilter,
103
        ) ([]model.Device, error)
104
        GetDevice(ctx context.Context, dev_id string) (*model.Device, error)
105
        DecommissionDevice(ctx context.Context, dev_id string) error
106
        DeleteDevice(ctx context.Context, dev_id string) error
107
        DeleteAuthSet(ctx context.Context, dev_id string, auth_id string) error
108
        AcceptDeviceAuth(ctx context.Context, dev_id string, auth_id string) error
109
        RejectDeviceAuth(ctx context.Context, dev_id string, auth_id string) error
110
        ResetDeviceAuth(ctx context.Context, dev_id string, auth_id string) error
111
        PreauthorizeDevice(ctx context.Context, req *model.PreAuthReq) (*model.Device, error)
112

113
        RevokeToken(ctx context.Context, tokenID string) error
114
        VerifyToken(ctx context.Context, token string) error
115
        DeleteTokens(ctx context.Context, tenantID, deviceID string) error
116

117
        SetTenantLimit(ctx context.Context, tenant_id string, limit model.Limit) error
118
        DeleteTenantLimit(ctx context.Context, tenant_id string, limit string) error
119

120
        GetLimit(ctx context.Context, name string) (*model.Limit, error)
121
        GetTenantLimit(ctx context.Context, name, tenant_id string) (*model.Limit, error)
122

123
        GetDevCountByStatus(ctx context.Context, status string) (int, error)
124

125
        ProvisionTenant(ctx context.Context, tenant_id string) error
126

127
        GetTenantDeviceStatus(ctx context.Context, tenantId, deviceId string) (*model.Status, error)
128
}
129

130
type DevAuth struct {
131
        db           store.DataStore
132
        invClient    inventory.Client
133
        cOrch        orchestrator.ClientRunner
134
        cTenant      tenant.ClientRunner
135
        jwt          jwt.Handler
136
        clientGetter ApiClientGetter
137
        verifyTenant bool
138
        config       Config
139
        cache        cache.Cache
140
        clock        utils.Clock
141
        checker      access.Checker
142
}
143

144
type Config struct {
145
        // token issuer
146
        Issuer string
147
        // token expiration time
148
        ExpirationTime int64
149
        // Default tenant token to use when the client supplies none. Can be
150
        // empty
151
        DefaultTenantToken string
152
        InventoryAddr      string
153

154
        EnableReporting bool
155
        HaveAddons      bool
156
}
157

158
func NewDevAuth(d store.DataStore, co orchestrator.ClientRunner,
159
        jwt jwt.Handler, config Config) *DevAuth {
299✔
160
        // initialize checker using an empty merge (returns nil on validate)
299✔
161
        checker := access.Merge()
299✔
162
        if config.HaveAddons {
300✔
163
                checker = access.NewAddonChecker()
1✔
164
        }
1✔
165

166
        return &DevAuth{
299✔
167
                db:           d,
299✔
168
                invClient:    inventory.NewClient(config.InventoryAddr, false),
299✔
169
                cOrch:        co,
299✔
170
                jwt:          jwt,
299✔
171
                clientGetter: simpleApiClientGetter,
299✔
172
                verifyTenant: false,
299✔
173
                config:       config,
299✔
174
                clock:        utils.NewClock(),
299✔
175
                checker:      checker,
299✔
176
        }
299✔
177
}
178

179
func (d *DevAuth) HealthCheck(ctx context.Context) error {
12✔
180
        err := d.db.Ping(ctx)
12✔
181
        if err != nil {
14✔
182
                return errors.Wrap(err, "error reaching MongoDB")
2✔
183
        }
2✔
184
        err = d.invClient.CheckHealth(ctx)
10✔
185
        if err != nil {
12✔
186
                return errors.Wrap(err, "Inventory service unhealthy")
2✔
187
        }
2✔
188
        err = d.cOrch.CheckHealth(ctx)
8✔
189
        if err != nil {
10✔
190
                return errors.Wrap(err, "Workflows service unhealthy")
2✔
191
        }
2✔
192
        if d.verifyTenant {
10✔
193
                err = d.cTenant.CheckHealth(ctx)
4✔
194
                if err != nil {
6✔
195
                        return errors.Wrap(err, "Tenantadm service unhealthy")
2✔
196
                }
2✔
197
        }
198
        return nil
4✔
199
}
200

201
func (d *DevAuth) setDeviceIdentity(ctx context.Context, dev *model.Device, tenantId string) error {
7✔
202
        attributes := make([]model.DeviceAttribute, len(dev.IdDataStruct))
7✔
203
        i := 0
7✔
204
        for name, value := range dev.IdDataStruct {
14✔
205
                if name == "status" {
7✔
206
                        //we have to forbid the client to override attribute status in identity scope
×
207
                        //since it stands for status of a device (as in: accepted, rejected, preauthorized)
×
208
                        continue
×
209
                }
210
                attribute := model.DeviceAttribute{
7✔
211
                        Name:        name,
7✔
212
                        Description: nil,
7✔
213
                        Value:       value,
7✔
214
                        Scope:       "identity",
7✔
215
                }
7✔
216
                attributes[i] = attribute
7✔
217
                i++
7✔
218
        }
219
        attrJson, err := json.Marshal(attributes)
7✔
220
        if err != nil {
7✔
221
                return errors.New("internal error: cannot marshal attributes into json")
×
222
        }
×
223
        if err := d.cOrch.SubmitUpdateDeviceInventoryJob(
7✔
224
                ctx,
7✔
225
                orchestrator.UpdateDeviceInventoryReq{
7✔
226
                        RequestId:  requestid.FromContext(ctx),
7✔
227
                        TenantId:   tenantId,
7✔
228
                        DeviceId:   dev.Id,
7✔
229
                        Scope:      "identity",
7✔
230
                        Attributes: string(attrJson),
7✔
231
                }); err != nil {
7✔
232
                return errors.Wrap(err, "failed to start device inventory update job")
×
233
        }
×
234
        if d.config.EnableReporting {
10✔
235
                if err := d.cOrch.SubmitReindexReporting(ctx, string(dev.Id)); err != nil {
3✔
236
                        return errors.Wrap(err, "reindex reporting job error")
×
237
                }
×
238
        }
239
        return nil
7✔
240
}
241

242
func (d *DevAuth) getDeviceFromAuthRequest(
243
        ctx context.Context,
244
        r *model.AuthReq,
245
) (*model.Device, error) {
23✔
246
        dev := model.NewDevice("", r.IdData, r.PubKey)
23✔
247

23✔
248
        l := log.FromContext(ctx)
23✔
249

23✔
250
        idDataStruct, idDataSha256, err := parseIdData(r.IdData)
23✔
251
        if err != nil {
23✔
252
                return nil, MakeErrDevAuthBadRequest(err)
×
253
        }
×
254

255
        dev.IdDataStruct = idDataStruct
23✔
256
        dev.IdDataSha256 = idDataSha256
23✔
257

23✔
258
        // record device
23✔
259
        err = d.db.AddDevice(ctx, *dev)
23✔
260
        addDeviceErr := err
23✔
261
        if err != nil && err != store.ErrObjectExists {
25✔
262
                l.Errorf("failed to add/find device: %v", err)
2✔
263
                return nil, err
2✔
264
        }
2✔
265

266
        // either the device was added or it was already present, in any case,
267
        // pull it from DB
268
        dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
21✔
269
        if err != nil {
23✔
270
                l.Error("failed to find device but could not add either")
2✔
271
                return nil, errors.New("failed to locate device")
2✔
272
        }
2✔
273

274
        idData := identity.FromContext(ctx)
19✔
275
        tenantId := ""
19✔
276
        if idData != nil {
28✔
277
                tenantId = idData.Tenant
9✔
278
        }
9✔
279
        if addDeviceErr != store.ErrObjectExists {
22✔
280
                if err := d.setDeviceIdentity(ctx, dev, tenantId); err != nil {
3✔
281
                        return nil, err
×
282
                }
×
283
        }
284

285
        // check if the device is in the decommissioning state
286
        if dev.Decommissioning {
19✔
287
                l.Warnf("Device %s in the decommissioning state.", dev.Id)
×
288
                return nil, ErrDevAuthUnauthorized
×
289
        }
×
290

291
        return dev, nil
19✔
292
}
293

294
func (d *DevAuth) signToken(ctx context.Context) jwt.SignFunc {
11✔
295
        return func(t *jwt.Token) (string, error) {
22✔
296
                return d.jwt.ToJWT(t)
11✔
297
        }
11✔
298
}
299

300
// tenantWithContext will update `ctx` with tenant related data
301
func tenantWithContext(ctx context.Context, tenantToken string) (context.Context, error) {
13✔
302
        ident, err := identity.ExtractIdentity(tenantToken)
13✔
303
        if err != nil {
17✔
304
                return nil, errors.Wrap(err, "failed to extract identity from tenant token")
4✔
305
        }
4✔
306

307
        // update context to store the identity of the caller
308
        ctx = identity.WithContext(ctx, &ident)
9✔
309

9✔
310
        // setup authorization header so that outgoing requests are done for
9✔
311
        // *this* tenant
9✔
312
        ctx = ctxhttpheader.WithContext(ctx,
9✔
313
                http.Header{
9✔
314
                        "Authorization": []string{fmt.Sprintf("Bearer %s", tenantToken)},
9✔
315
                },
9✔
316
                "Authorization")
9✔
317

9✔
318
        return ctx, nil
9✔
319
}
320

321
func (d *DevAuth) doVerifyTenant(ctx context.Context, token string) (*tenant.Tenant, error) {
23✔
322
        t, err := d.cTenant.VerifyToken(ctx, token)
23✔
323

23✔
324
        if err != nil {
34✔
325
                if tenant.IsErrTokenVerificationFailed(err) {
20✔
326
                        return nil, MakeErrDevAuthUnauthorized(err)
9✔
327
                }
9✔
328

329
                return nil, errors.Wrap(err, "request to verify tenant token failed")
3✔
330
        }
331

332
        return t, nil
13✔
333
}
334

335
func (d *DevAuth) getTenantWithDefault(
336
        ctx context.Context,
337
        tenantToken,
338
        defaultToken string,
339
) (context.Context, *tenant.Tenant, error) {
23✔
340
        l := log.FromContext(ctx)
23✔
341

23✔
342
        if tenantToken == "" && defaultToken == "" {
26✔
343
                return nil, nil, MakeErrDevAuthUnauthorized(errors.New("tenant token missing"))
3✔
344
        }
3✔
345

346
        var chosenToken string
21✔
347
        var t *tenant.Tenant
21✔
348
        var err error
21✔
349

21✔
350
        // try the provided token
21✔
351
        // but continue on errors and maybe try the default token
21✔
352
        if tenantToken != "" {
38✔
353
                t, err = d.doVerifyTenant(ctx, tenantToken)
17✔
354

17✔
355
                if err == nil {
28✔
356
                        chosenToken = tenantToken
11✔
357
                } else {
18✔
358
                        l.Errorf("Failed to verify supplied tenant token: %s", err.Error())
7✔
359
                }
7✔
360
        }
361

362
        // if we still haven't selected a tenant - the token didn't work
363
        // try the default one
364
        if t == nil && defaultToken != "" {
27✔
365
                t, err = d.doVerifyTenant(ctx, defaultToken)
6✔
366

6✔
367
                if err == nil {
8✔
368
                        chosenToken = defaultToken
2✔
369
                }
2✔
370
                if err != nil {
10✔
371
                        l.Errorf("Failed to verify default tenant token: %s", err.Error())
4✔
372
                }
4✔
373
        }
374

375
        // none of the tokens worked
376
        if err != nil {
30✔
377
                if tenant.IsErrTokenVerificationFailed(err) {
9✔
378
                        return ctx, nil, MakeErrDevAuthUnauthorized(err)
×
379
                }
×
380
                return ctx, nil, err
9✔
381
        }
382

383
        // we do have a working token/valid tenant
384
        tCtx, err := tenantWithContext(ctx, chosenToken)
13✔
385
        if err != nil {
17✔
386
                l.Errorf("failed to setup tenant context: %v", err)
4✔
387
                return nil, nil, ErrDevAuthUnauthorized
4✔
388
        }
4✔
389

390
        return tCtx, t, nil
9✔
391
}
392

393
func (d *DevAuth) SubmitAuthRequest(ctx context.Context, r *model.AuthReq) (string, error) {
53✔
394
        l := log.FromContext(ctx)
53✔
395

53✔
396
        var tenant *tenant.Tenant
53✔
397
        var err error
53✔
398

53✔
399
        if d.verifyTenant {
76✔
400
                ctx, tenant, err = d.getTenantWithDefault(ctx, r.TenantToken, d.config.DefaultTenantToken)
23✔
401
                if err != nil {
38✔
402
                        return "", err
15✔
403
                }
15✔
404
        } else {
31✔
405
                // ignore identity data when tenant verification is off
31✔
406
                // it's possible that the device will provide old auth token or old tenant token
31✔
407
                // in the authorization header;
31✔
408
                // in that case we need to wipe identity data from the context
31✔
409
                ctx = identity.WithContext(ctx, nil)
31✔
410
        }
31✔
411

412
        // first, try to handle preauthorization
413
        authSet, err := d.processPreAuthRequest(ctx, r)
39✔
414
        if err != nil {
51✔
415
                return "", err
12✔
416
        }
12✔
417

418
        // if not a preauth request, process with regular auth request handling
419
        if authSet == nil {
50✔
420
                authSet, err = d.processAuthRequest(ctx, r)
23✔
421
                if err != nil {
29✔
422
                        return "", err
6✔
423
                }
6✔
424
        }
425

426
        // request was already present in DB, check its status
427
        if authSet.Status == model.DevStatusAccepted {
32✔
428
                jti := oid.FromString(authSet.Id)
11✔
429
                if jti.String() == "" {
11✔
430
                        return "", ErrInvalidAuthSetID
×
431
                }
×
432
                sub := oid.FromString(authSet.DeviceId)
11✔
433
                if sub.String() == "" {
11✔
434
                        return "", ErrInvalidDeviceID
×
435
                }
×
436
                now := time.Now()
11✔
437
                token := &jwt.Token{Claims: jwt.Claims{
11✔
438
                        ID:      jti,
11✔
439
                        Subject: sub,
11✔
440
                        Issuer:  d.config.Issuer,
11✔
441
                        ExpiresAt: jwt.Time{
11✔
442
                                Time: now.Add(time.Second *
11✔
443
                                        time.Duration(d.config.ExpirationTime)),
11✔
444
                        },
11✔
445
                        IssuedAt: jwt.Time{Time: now},
11✔
446
                        Device:   true,
11✔
447
                }}
11✔
448

11✔
449
                if d.verifyTenant {
16✔
450
                        token.Claims.Tenant = tenant.ID
5✔
451
                        token.Claims.Plan = tenant.Plan
5✔
452
                        token.Claims.Addons = tenant.Addons
5✔
453
                        token.Claims.Trial = tenant.Trial
5✔
454
                } else {
12✔
455
                        token.Claims.Plan = plan.PlanEnterprise
7✔
456
                        token.Addons = addons.AllAddonsEnabled
7✔
457
                }
7✔
458

459
                // sign and encode as JWT
460
                raw, err := token.MarshalJWT(d.signToken(ctx))
11✔
461
                if err != nil {
11✔
462
                        return "", errors.Wrap(err, "generate token error")
×
463
                }
×
464

465
                if err := d.db.AddToken(ctx, token); err != nil {
11✔
466
                        return "", errors.Wrap(err, "add token error")
×
467
                }
×
468

469
                l.Infof("Token %s assigned to device %s",
11✔
470
                        token.Claims.ID, token.Claims.Subject)
11✔
471
                _ = d.updateCheckInTime(ctx, authSet.DeviceId, token.Claims.Tenant, nil)
11✔
472
                return string(raw), nil
11✔
473
        }
474

475
        // no token, return device unauthorized
476
        return "", ErrDevAuthUnauthorized
11✔
477

478
}
479

480
func (d *DevAuth) processPreAuthRequest(
481
        ctx context.Context,
482
        r *model.AuthReq,
483
) (*model.AuthSet, error) {
39✔
484
        var deviceAlreadyAccepted bool
39✔
485

39✔
486
        _, idDataSha256, err := parseIdData(r.IdData)
39✔
487
        if err != nil {
41✔
488
                return nil, MakeErrDevAuthBadRequest(err)
2✔
489
        }
2✔
490

491
        // authset exists?
492
        aset, err := d.db.GetAuthSetByIdDataHashKey(ctx, idDataSha256, r.PubKey)
37✔
493
        switch err {
37✔
494
        case nil:
33✔
495
                break
33✔
496
        case store.ErrAuthSetNotFound:
3✔
497
                return nil, nil
3✔
498
        default:
2✔
499
                return nil, errors.Wrap(err, "failed to fetch auth set")
2✔
500
        }
501

502
        // if authset status is not 'preauthorized', nothing to do
503
        if aset.Status != model.DevStatusPreauth {
54✔
504
                return nil, nil
21✔
505
        }
21✔
506

507
        // check the device status
508
        // if the device status is accepted then do not trigger provisioning workflow
509
        // this needs to be checked before changing authentication set status
510
        dev, err := d.db.GetDeviceById(ctx, aset.DeviceId)
12✔
511
        if err != nil {
14✔
512
                return nil, err
2✔
513
        }
2✔
514

515
        currentStatus := dev.Status
10✔
516
        if dev.Status == model.DevStatusAccepted {
12✔
517
                deviceAlreadyAccepted = true
2✔
518
        }
2✔
519

520
        // auth set is ok for auto-accepting, check device limit
521
        allow, err := d.canAcceptDevice(ctx)
10✔
522
        if err != nil {
12✔
523
                return nil, err
2✔
524
        }
2✔
525

526
        if !allow {
10✔
527
                return nil, ErrMaxDeviceCountReached
2✔
528
        }
2✔
529

530
        update := model.AuthSetUpdate{
6✔
531
                Status: model.DevStatusAccepted,
6✔
532
        }
6✔
533
        // persist the 'accepted' status in both auth set, and device
6✔
534
        if err := d.db.UpdateAuthSetById(ctx, aset.Id, update); err != nil {
6✔
535
                return nil, errors.Wrap(err, "failed to update auth set status")
×
536
        }
×
537

538
        if err := d.updateDeviceStatus(
6✔
539
                ctx,
6✔
540
                aset.DeviceId,
6✔
541
                model.DevStatusAccepted,
6✔
542
                currentStatus,
6✔
543
        ); err != nil {
6✔
544
                return nil, err
×
545
        }
×
546

547
        aset.Status = model.DevStatusAccepted
6✔
548
        dev.Status = model.DevStatusAccepted
6✔
549
        dev.AuthSets = append(dev.AuthSets, *aset)
6✔
550

6✔
551
        if !deviceAlreadyAccepted {
10✔
552
                reqId := requestid.FromContext(ctx)
4✔
553
                var tenantID string
4✔
554
                if idty := identity.FromContext(ctx); idty != nil {
4✔
555
                        tenantID = idty.Tenant
×
556
                }
×
557

558
                // submit device accepted job
559
                if err := d.cOrch.SubmitProvisionDeviceJob(
4✔
560
                        ctx,
4✔
561
                        orchestrator.ProvisionDeviceReq{
4✔
562
                                RequestId: reqId,
4✔
563
                                DeviceID:  aset.DeviceId,
4✔
564
                                TenantID:  tenantID,
4✔
565
                                Device:    dev,
4✔
566
                                Status:    dev.Status,
4✔
567
                        }); err != nil {
6✔
568
                        return nil, errors.Wrap(err, "submit device provisioning job error")
2✔
569
                }
2✔
570
        }
571
        return aset, nil
4✔
572
}
573

574
func (d *DevAuth) updateDeviceStatus(
575
        ctx context.Context,
576
        devId,
577
        status string,
578
        currentStatus string,
579
) error {
65✔
580
        newStatus, err := d.db.GetDeviceStatus(ctx, devId)
65✔
581
        if currentStatus == newStatus {
70✔
582
                return nil
5✔
583
        }
5✔
584
        if status == "" {
110✔
585
                switch err {
49✔
586
                case nil:
45✔
587
                        status = newStatus
45✔
588
                case store.ErrAuthSetNotFound:
3✔
589
                        status = model.DevStatusNoAuth
3✔
590
                default:
2✔
591
                        return errors.Wrap(err, "Cannot determine device status")
2✔
592
                }
593
        }
594

595
        // submit device status change job
596
        dev, err := d.db.GetDeviceById(ctx, devId)
59✔
597
        if err != nil {
59✔
598
                return errors.Wrap(err, "db get device by id error")
×
599
        }
×
600

601
        tenantId := ""
59✔
602
        idData := identity.FromContext(ctx)
59✔
603
        if idData != nil {
72✔
604
                tenantId = idData.Tenant
13✔
605
        }
13✔
606
        req := orchestrator.UpdateDeviceStatusReq{
59✔
607
                RequestId: requestid.FromContext(ctx),
59✔
608
                Devices: []model.DeviceInventoryUpdate{{
59✔
609
                        Id:       dev.Id,
59✔
610
                        Revision: dev.Revision + 1,
59✔
611
                }},
59✔
612
                TenantId: tenantId,
59✔
613
                Status:   status,
59✔
614
        }
59✔
615
        if err := d.cOrch.SubmitUpdateDeviceStatusJob(ctx, req); err != nil {
60✔
616
                return errors.Wrap(err, "update device status job error")
1✔
617
        }
1✔
618

619
        if err := d.db.UpdateDevice(ctx,
59✔
620
                devId,
59✔
621
                model.DeviceUpdate{
59✔
622
                        Status:    status,
59✔
623
                        UpdatedTs: uto.TimePtr(time.Now().UTC()),
59✔
624
                }); err != nil {
61✔
625
                return errors.Wrap(err, "failed to update device status")
2✔
626
        }
2✔
627

628
        if d.config.EnableReporting {
62✔
629
                if err := d.cOrch.SubmitReindexReporting(ctx, devId); err != nil {
7✔
630
                        return errors.Wrap(err, "reindex reporting job error")
2✔
631
                }
2✔
632
        }
633

634
        return nil
55✔
635
}
636

637
// processAuthRequest will process incoming auth request and record authentication
638
// data information it contains. Returns a tupe (auth set, error). If no errors were
639
// present, model.AuthSet.Status will indicate the status of device admission
640
func (d *DevAuth) processAuthRequest(
641
        ctx context.Context,
642
        r *model.AuthReq,
643
) (*model.AuthSet, error) {
23✔
644

23✔
645
        l := log.FromContext(ctx)
23✔
646

23✔
647
        // get device associated with given authorization request
23✔
648
        dev, err := d.getDeviceFromAuthRequest(ctx, r)
23✔
649
        if err != nil {
27✔
650
                return nil, err
4✔
651
        }
4✔
652

653
        idDataStruct, idDataSha256, err := parseIdData(r.IdData)
19✔
654
        if err != nil {
19✔
655
                return nil, MakeErrDevAuthBadRequest(err)
×
656
        }
×
657

658
        areq := &model.AuthSet{
19✔
659
                Id:           oid.NewUUIDv4().String(),
19✔
660
                IdData:       r.IdData,
19✔
661
                IdDataStruct: idDataStruct,
19✔
662
                IdDataSha256: idDataSha256,
19✔
663
                PubKey:       r.PubKey,
19✔
664
                DeviceId:     dev.Id,
19✔
665
                Status:       model.DevStatusPending,
19✔
666
                Timestamp:    uto.TimePtr(time.Now()),
19✔
667
        }
19✔
668

19✔
669
        // record authentication request
19✔
670
        err = d.db.AddAuthSet(ctx, *areq)
19✔
671
        if err != nil && err != store.ErrObjectExists {
19✔
672
                return nil, err
×
673
        }
×
674

675
        // update the device status
676
        if err := d.updateDeviceStatus(ctx, dev.Id, "", dev.Status); err != nil {
19✔
677
                return nil, err
×
678
        }
×
679

680
        // either the request was added or it was already present in the DB, get
681
        // it now
682
        areq, err = d.db.GetAuthSetByIdDataHashKey(ctx, idDataSha256, r.PubKey)
19✔
683
        if err != nil {
21✔
684
                l.Error("failed to find device auth set but could not add one either")
2✔
685
                return nil, errors.New("failed to locate device auth set")
2✔
686
        }
2✔
687

688
        return areq, nil
17✔
689
}
690

691
func (d *DevAuth) GetDevices(
692
        ctx context.Context,
693
        skip,
694
        limit uint,
695
        filter model.DeviceFilter,
696
) ([]model.Device, error) {
5✔
697
        devs, err := d.db.GetDevices(ctx, skip, limit, filter)
5✔
698
        if err != nil {
5✔
699
                return nil, errors.Wrap(err, "failed to list devices")
×
700
        }
×
701

702
        for i := range devs {
10✔
703
                devs[i].AuthSets, err = d.db.GetAuthSetsForDevice(ctx, devs[i].Id)
5✔
704
                if err != nil && err != store.ErrAuthSetNotFound {
5✔
705
                        return nil, errors.Wrap(err, "db get auth sets error")
×
706
                }
×
707
        }
708

709
        // update check-in time
710
        if d.cache != nil {
9✔
711
                tenantID := ""
4✔
712
                idData := identity.FromContext(ctx)
4✔
713
                if idData != nil {
8✔
714
                        tenantID = idData.Tenant
4✔
715
                }
4✔
716

717
                ids := make([]string, len(devs))
4✔
718
                for i := range devs {
8✔
719
                        ids[i] = devs[i].Id
4✔
720
                }
4✔
721
                checkInTimes, err := d.cache.GetCheckInTimes(ctx, tenantID, ids)
4✔
722
                if err != nil {
6✔
723
                        l := log.FromContext(ctx)
2✔
724
                        l.Errorf("Failed to get check-in times for devices")
2✔
725
                } else {
4✔
726
                        for i := range devs {
4✔
727
                                if checkInTimes[i] != nil {
4✔
728
                                        devs[i].CheckInTime = checkInTimes[i]
2✔
729
                                }
2✔
730
                        }
731
                }
732
        }
733

734
        return devs, err
5✔
735
}
736

737
func (d *DevAuth) GetDevice(ctx context.Context, devId string) (*model.Device, error) {
5✔
738
        dev, err := d.db.GetDeviceById(ctx, devId)
5✔
739
        if err != nil {
6✔
740
                if err != store.ErrDevNotFound {
1✔
741
                        return nil, errors.Wrap(err, "db get device by id error")
×
742
                }
×
743
                return nil, err
1✔
744
        }
745

746
        dev.AuthSets, err = d.db.GetAuthSetsForDevice(ctx, dev.Id)
5✔
747
        if err != nil {
5✔
748
                if err != store.ErrAuthSetNotFound {
×
749
                        return nil, errors.Wrap(err, "db get auth sets error")
×
750
                }
×
751
                return dev, nil
×
752
        }
753

754
        if d.cache != nil {
9✔
755
                tenantID := ""
4✔
756
                idData := identity.FromContext(ctx)
4✔
757
                if idData != nil {
8✔
758
                        tenantID = idData.Tenant
4✔
759
                }
4✔
760

761
                checkInTime, err := d.cache.GetCheckInTime(ctx, tenantID, devId)
4✔
762
                if err != nil {
6✔
763
                        l := log.FromContext(ctx)
2✔
764
                        l.Errorf("Failed to get check-in times for device")
2✔
765
                } else if checkInTime != nil {
6✔
766
                        dev.CheckInTime = checkInTime
2✔
767
                }
2✔
768
        }
769

770
        return dev, err
5✔
771
}
772

773
// DecommissionDevice deletes device and all its tokens
774
func (d *DevAuth) DecommissionDevice(ctx context.Context, devID string) error {
17✔
775

17✔
776
        l := log.FromContext(ctx)
17✔
777

17✔
778
        l.Warnf("Decommission device with id: %s", devID)
17✔
779

17✔
780
        err := d.cacheDeleteToken(ctx, devID)
17✔
781
        if err != nil {
23✔
782
                return errors.Wrapf(err, "failed to delete token for %s from cache", devID)
6✔
783
        }
6✔
784

785
        // set decommissioning flag on the device
786
        updev := model.DeviceUpdate{
11✔
787
                Decommissioning: uto.BoolPtr(true),
11✔
788
        }
11✔
789
        if err := d.db.UpdateDevice(
11✔
790
                ctx, devID, updev,
11✔
791
        ); err != nil {
16✔
792
                return err
5✔
793
        }
5✔
794

795
        reqId := requestid.FromContext(ctx)
7✔
796

7✔
797
        tenantID := ""
7✔
798
        idData := identity.FromContext(ctx)
7✔
799
        if idData != nil {
9✔
800
                tenantID = idData.Tenant
2✔
801
        }
2✔
802

803
        // submit device decommissioning job
804
        if err := d.cOrch.SubmitDeviceDecommisioningJob(
7✔
805
                ctx,
7✔
806
                orchestrator.DecommissioningReq{
7✔
807
                        DeviceId:  devID,
7✔
808
                        RequestId: reqId,
7✔
809
                        TenantID:  tenantID,
7✔
810
                }); err != nil {
10✔
811
                return errors.Wrap(err, "submit device decommissioning job error")
3✔
812
        }
3✔
813

814
        return err
5✔
815
}
816

817
// Delete a device and its tokens from deviceauth db
818
func (d *DevAuth) DeleteDevice(ctx context.Context, devID string) error {
13✔
819
        // delete device authorization sets
13✔
820
        if err := d.db.DeleteAuthSetsForDevice(ctx, devID); err != nil &&
13✔
821
                err != store.ErrAuthSetNotFound {
15✔
822
                return errors.Wrap(err, "db delete device authorization sets error")
2✔
823
        }
2✔
824

825
        devOID := oid.FromString(devID)
11✔
826
        // If the devID is not a valid string, there's no token.
11✔
827
        if devOID.String() == "" {
13✔
828
                return ErrInvalidDeviceID
2✔
829
        }
2✔
830
        // delete device tokens
831
        if err := d.db.DeleteTokenByDevId(
9✔
832
                ctx, devOID,
9✔
833
        ); err != nil && err != store.ErrTokenNotFound {
11✔
834
                return errors.Wrap(err, "db delete device tokens error")
2✔
835
        }
2✔
836

837
        // delete device
838
        if err := d.db.DeleteDevice(ctx, devID); err != nil {
11✔
839
                return err
4✔
840
        }
4✔
841

842
        if d.config.EnableReporting {
4✔
843
                if err := d.cOrch.SubmitReindexReporting(ctx, devID); err != nil {
1✔
844
                        return errors.Wrap(err, "reindex reporting job error")
×
845
                }
×
846
        }
847

848
        return nil
3✔
849
}
850

851
// Deletes device authentication set, and optionally the device.
852
func (d *DevAuth) DeleteAuthSet(ctx context.Context, devID string, authId string) error {
39✔
853

39✔
854
        l := log.FromContext(ctx)
39✔
855

39✔
856
        l.Warnf("Delete authentication set with id: "+
39✔
857
                "%s for the device with id: %s",
39✔
858
                authId, devID)
39✔
859

39✔
860
        err := d.cacheDeleteToken(ctx, devID)
39✔
861
        if err != nil {
43✔
862
                return errors.Wrapf(err, "failed to delete token for %s from cache", devID)
4✔
863
        }
4✔
864

865
        // retrieve device authentication set to check its status
866
        authSet, err := d.db.GetAuthSetById(ctx, authId)
35✔
867
        if err != nil {
40✔
868
                if err == store.ErrAuthSetNotFound {
8✔
869
                        return err
3✔
870
                }
3✔
871
                return errors.Wrap(err, "db get auth set error")
2✔
872
        }
873

874
        // delete device authorization set
875
        if err := d.db.DeleteAuthSetForDevice(ctx, devID, authId); err != nil {
33✔
876
                return err
2✔
877
        }
2✔
878

879
        // if the device authentication set is accepted delete device tokens
880
        if authSet.Status == model.DevStatusAccepted {
31✔
881
                // If string is not a valid UUID there's no token.
2✔
882
                devOID := oid.FromString(devID)
2✔
883
                if err := d.db.DeleteTokenByDevId(
2✔
884
                        ctx, devOID,
2✔
885
                ); err != nil && err != store.ErrTokenNotFound {
4✔
886
                        return errors.Wrap(err,
2✔
887
                                "db delete device tokens error")
2✔
888
                }
2✔
889
        } else if authSet.Status == model.DevStatusPreauth {
33✔
890
                // if the auth set status is 'preauthorized', the device is deleted from
6✔
891
                // deviceauth. We cannot start the decommission_device workflow because
6✔
892
                // we don't provision devices until they are accepted. Still, we need to
6✔
893
                // remove the device from the inventory service because we index pre-authorized
6✔
894
                // devices for consumption via filtering APIs. To trigger the deletion
6✔
895
                // from the inventory service, we start the status update workflow with the
6✔
896
                // special value "decommissioned", which will cause the deletion of the
6✔
897
                // device from the inventory service's database
6✔
898
                tenantID := ""
6✔
899
                idData := identity.FromContext(ctx)
6✔
900
                if idData != nil {
6✔
901
                        tenantID = idData.Tenant
×
902
                }
×
903
                req := orchestrator.UpdateDeviceStatusReq{
6✔
904
                        RequestId: requestid.FromContext(ctx),
6✔
905
                        Devices:   []model.DeviceInventoryUpdate{{Id: devID}},
6✔
906
                        TenantId:  tenantID,
6✔
907
                        Status:    "decommissioned",
6✔
908
                }
6✔
909
                if err = d.cOrch.SubmitUpdateDeviceStatusJob(ctx, req); err != nil {
8✔
910
                        return errors.Wrap(err, "update device status job error")
2✔
911
                }
2✔
912

913
                // delete device
914
                if err := d.db.DeleteDevice(ctx, devID); err != nil {
6✔
915
                        return err
2✔
916
                }
2✔
917

918
                if d.config.EnableReporting {
4✔
919
                        if err := d.cOrch.SubmitReindexReporting(ctx, devID); err != nil {
4✔
920
                                return errors.Wrap(err, "reindex reporting job error")
2✔
921
                        }
2✔
922
                }
923

924
                return nil
×
925
        }
926

927
        return d.updateDeviceStatus(ctx, devID, "", authSet.Status)
21✔
928
}
929

930
func (d *DevAuth) AcceptDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
31✔
931
        l := log.FromContext(ctx)
31✔
932

31✔
933
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
31✔
934
        if err != nil {
34✔
935
                if err == store.ErrAuthSetNotFound {
6✔
936
                        return err
3✔
937
                }
3✔
938
                return errors.Wrap(err, "db get auth set error")
×
939
        }
940

941
        // device authentication set already accepted, nothing to do here
942
        if aset.Status == model.DevStatusAccepted {
31✔
943
                l.Debugf("Device %s already accepted", device_id)
2✔
944
                return nil
2✔
945
        } else if aset.Status != model.DevStatusRejected && aset.Status != model.DevStatusPending {
31✔
946
                // device authentication set can be accepted only from 'pending' or 'rejected' statuses
2✔
947
                return ErrDevAuthBadRequest
2✔
948
        }
2✔
949

950
        // check the device status
951
        // if the device status is accepted then do not trigger provisioning workflow
952
        // this needs to be checked before changing authentication set status
953
        dev, err := d.db.GetDeviceById(ctx, device_id)
25✔
954
        if err != nil {
27✔
955
                return err
2✔
956
        }
2✔
957

958
        // possible race, consider accept-count-unaccept pattern if that's problematic
959
        allow, err := d.canAcceptDevice(ctx)
23✔
960
        if err != nil {
27✔
961
                return err
4✔
962
        }
4✔
963

964
        if !allow {
24✔
965
                return ErrMaxDeviceCountReached
5✔
966
        }
5✔
967

968
        if err := d.setAuthSetStatus(ctx, device_id, auth_id, model.DevStatusAccepted); err != nil {
20✔
969
                return err
5✔
970
        }
5✔
971

972
        if dev.Status != model.DevStatusPending {
14✔
973
                // Device already exist in all services
3✔
974
                // We're done...
3✔
975
                return nil
3✔
976
        }
3✔
977

978
        dev.Status = model.DevStatusAccepted
9✔
979
        aset.Status = model.DevStatusAccepted
9✔
980
        dev.AuthSets = []model.AuthSet{*aset}
9✔
981

9✔
982
        reqId := requestid.FromContext(ctx)
9✔
983

9✔
984
        var tenantID string
9✔
985
        if idty := identity.FromContext(ctx); idty != nil {
10✔
986
                tenantID = idty.Tenant
1✔
987
        }
1✔
988

989
        // submit device accepted job
990
        if err := d.cOrch.SubmitProvisionDeviceJob(
9✔
991
                ctx,
9✔
992
                orchestrator.ProvisionDeviceReq{
9✔
993
                        RequestId: reqId,
9✔
994
                        DeviceID:  aset.DeviceId,
9✔
995
                        TenantID:  tenantID,
9✔
996
                        Device:    dev,
9✔
997
                        Status:    dev.Status,
9✔
998
                }); err != nil {
11✔
999
                return errors.Wrap(err, "submit device provisioning job error")
2✔
1000
        }
2✔
1001

1002
        return nil
7✔
1003
}
1004

1005
func (d *DevAuth) setAuthSetStatus(
1006
        ctx context.Context,
1007
        deviceID string,
1008
        authID string,
1009
        status string,
1010
) error {
29✔
1011
        aset, err := d.db.GetAuthSetById(ctx, authID)
29✔
1012
        if err != nil {
29✔
1013
                if err == store.ErrAuthSetNotFound {
×
1014
                        return err
×
1015
                }
×
1016
                return errors.Wrap(err, "db get auth set error")
×
1017
        }
1018

1019
        if aset.DeviceId != deviceID {
29✔
1020
                return ErrDevIdAuthIdMismatch
×
1021
        }
×
1022

1023
        if aset.Status == status {
29✔
1024
                return nil
×
1025
        }
×
1026

1027
        currentStatus := aset.Status
29✔
1028

29✔
1029
        if aset.Status == model.DevStatusAccepted &&
29✔
1030
                (status == model.DevStatusRejected || status == model.DevStatusPending) {
42✔
1031
                deviceOID := oid.FromString(aset.DeviceId)
13✔
1032
                // delete device token
13✔
1033
                err := d.db.DeleteTokenByDevId(ctx, deviceOID)
13✔
1034
                if err != nil && err != store.ErrTokenNotFound {
17✔
1035
                        return errors.Wrap(err, "db delete device token error")
4✔
1036
                }
4✔
1037
        }
1038

1039
        // if accepting an auth set
1040
        if status == model.DevStatusAccepted {
40✔
1041
                // reject all accepted auth sets for this device first
15✔
1042
                if err := d.db.UpdateAuthSet(ctx,
15✔
1043
                        bson.M{
15✔
1044
                                model.AuthSetKeyDeviceId: deviceID,
15✔
1045
                                "$or": []bson.M{
15✔
1046
                                        {model.AuthSetKeyStatus: model.DevStatusAccepted},
15✔
1047
                                        {model.AuthSetKeyStatus: model.DevStatusPreauth},
15✔
1048
                                },
15✔
1049
                        },
15✔
1050
                        model.AuthSetUpdate{
15✔
1051
                                Status: model.DevStatusRejected,
15✔
1052
                        }); err != nil && err != store.ErrAuthSetNotFound {
17✔
1053
                        return errors.Wrap(err, "failed to reject auth sets")
2✔
1054
                }
2✔
1055
        }
1056

1057
        if err := d.db.UpdateAuthSetById(ctx, aset.Id, model.AuthSetUpdate{
23✔
1058
                Status: status,
23✔
1059
        }); err != nil {
25✔
1060
                return errors.Wrap(err, "db update device auth set error")
2✔
1061
        }
2✔
1062

1063
        if status == model.DevStatusAccepted {
32✔
1064
                return d.updateDeviceStatus(ctx, deviceID, status, currentStatus)
11✔
1065
        }
11✔
1066
        return d.updateDeviceStatus(ctx, deviceID, "", currentStatus)
11✔
1067
}
1068

1069
func (d *DevAuth) RejectDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
17✔
1070
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
17✔
1071
        if err != nil {
20✔
1072
                if err == store.ErrAuthSetNotFound {
4✔
1073
                        return err
1✔
1074
                }
1✔
1075
                return errors.Wrap(err, "db get auth set error")
2✔
1076
        } else if aset.Status != model.DevStatusPending && aset.Status != model.DevStatusAccepted {
17✔
1077
                // device authentication set can be rejected only from 'accepted' or 'pending' statuses
2✔
1078
                return ErrDevAuthBadRequest
2✔
1079
        }
2✔
1080

1081
        err = d.cacheDeleteToken(ctx, device_id)
13✔
1082
        if err != nil {
17✔
1083
                return errors.Wrapf(err, "failed to delete token for %s from cache", device_id)
4✔
1084
        }
4✔
1085

1086
        return d.setAuthSetStatus(ctx, device_id, auth_id, model.DevStatusRejected)
9✔
1087
}
1088

1089
func (d *DevAuth) ResetDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
9✔
1090
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
9✔
1091
        if err != nil {
11✔
1092
                if err == store.ErrAuthSetNotFound {
2✔
1093
                        return err
×
1094
                }
×
1095
                return errors.Wrap(err, "db get auth set error")
2✔
1096
        } else if aset.Status == model.DevStatusPreauth {
7✔
1097
                // preauthorized auth set should not go into pending state
×
1098
                return ErrDevAuthBadRequest
×
1099
        }
×
1100
        return d.setAuthSetStatus(ctx, device_id, auth_id, model.DevStatusPending)
7✔
1101
}
1102

1103
func parseIdData(idData string) (map[string]interface{}, []byte, error) {
99✔
1104
        var idDataStruct map[string]interface{}
99✔
1105
        var idDataSha256 []byte
99✔
1106

99✔
1107
        err := json.Unmarshal([]byte(idData), &idDataStruct)
99✔
1108
        if err != nil {
103✔
1109
                return idDataStruct, idDataSha256, errors.Wrapf(
4✔
1110
                        err,
4✔
1111
                        "failed to parse identity data: %s",
4✔
1112
                        idData,
4✔
1113
                )
4✔
1114
        }
4✔
1115

1116
        hash := sha256.New()
95✔
1117
        _, _ = hash.Write([]byte(idData))
95✔
1118
        idDataSha256 = hash.Sum(nil)
95✔
1119

95✔
1120
        return idDataStruct, idDataSha256, nil
95✔
1121
}
1122

1123
func (d *DevAuth) PreauthorizeDevice(
1124
        ctx context.Context,
1125
        req *model.PreAuthReq,
1126
) (*model.Device, error) {
14✔
1127
        // try add device, if a device with the given id_data exists -
14✔
1128
        // the unique index on id_data will prevent it (conflict)
14✔
1129
        // this is the only safeguard against id data conflict - we won't try to handle it
14✔
1130
        // additionally on inserting the auth set (can't add an id data index on auth set - would
14✔
1131
        // prevent key rotation)
14✔
1132

14✔
1133
        // FIXME: tenant_token is "" on purpose, will be removed
14✔
1134

14✔
1135
        l := log.FromContext(ctx)
14✔
1136

14✔
1137
        dev := model.NewDevice(req.DeviceId, req.IdData, req.PubKey)
14✔
1138
        dev.Status = model.DevStatusPreauth
14✔
1139

14✔
1140
        idDataStruct, idDataSha256, err := parseIdData(req.IdData)
14✔
1141
        if err != nil {
16✔
1142
                return nil, MakeErrDevAuthBadRequest(err)
2✔
1143
        }
2✔
1144

1145
        dev.IdDataStruct = idDataStruct
12✔
1146
        dev.IdDataSha256 = idDataSha256
12✔
1147

12✔
1148
        err = d.db.AddDevice(ctx, *dev)
12✔
1149
        switch err {
12✔
1150
        case nil:
8✔
1151
                break
8✔
1152
        case store.ErrObjectExists:
2✔
1153
                dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
2✔
1154
                if err != nil {
2✔
1155
                        l.Error("failed to find device but could not preauthorize either")
×
1156
                        return nil, errors.New("failed to preauthorize device")
×
1157
                }
×
1158
                return dev, ErrDeviceExists
2✔
1159
        default:
2✔
1160
                return nil, errors.Wrap(err, "failed to add device")
2✔
1161
        }
1162

1163
        tenantId := ""
8✔
1164
        idData := identity.FromContext(ctx)
8✔
1165
        if idData != nil {
8✔
1166
                tenantId = idData.Tenant
×
1167
        }
×
1168

1169
        wfReq := orchestrator.UpdateDeviceStatusReq{
8✔
1170
                RequestId: requestid.FromContext(ctx),
8✔
1171
                Devices: []model.DeviceInventoryUpdate{{
8✔
1172
                        Id:       dev.Id,
8✔
1173
                        Revision: dev.Revision,
8✔
1174
                }},
8✔
1175
                TenantId: tenantId,
8✔
1176
                Status:   dev.Status,
8✔
1177
        }
8✔
1178
        if err = d.cOrch.SubmitUpdateDeviceStatusJob(ctx, wfReq); err != nil {
8✔
1179
                return nil, errors.Wrap(err, "update device status job error")
×
1180
        }
×
1181

1182
        // record authentication request
1183
        authset := model.AuthSet{
8✔
1184
                Id:           req.AuthSetId,
8✔
1185
                IdData:       req.IdData,
8✔
1186
                IdDataStruct: idDataStruct,
8✔
1187
                IdDataSha256: idDataSha256,
8✔
1188
                PubKey:       req.PubKey,
8✔
1189
                DeviceId:     req.DeviceId,
8✔
1190
                Status:       model.DevStatusPreauth,
8✔
1191
                Timestamp:    uto.TimePtr(time.Now()),
8✔
1192
        }
8✔
1193

8✔
1194
        err = d.db.AddAuthSet(ctx, authset)
8✔
1195
        switch err {
8✔
1196
        case nil:
4✔
1197
                if err := d.setDeviceIdentity(ctx, dev, tenantId); err != nil {
4✔
1198
                        return nil, err
×
1199
                }
×
1200
                return nil, nil
4✔
1201
        case store.ErrObjectExists:
2✔
1202
                dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
2✔
1203
                if err != nil {
2✔
1204
                        l.Error("failed to find device but could not preauthorize either")
×
1205
                        return nil, errors.New("failed to preauthorize device")
×
1206
                }
×
1207
                return dev, ErrDeviceExists
2✔
1208
        default:
2✔
1209
                return nil, errors.Wrap(err, "failed to add auth set")
2✔
1210
        }
1211
}
1212

1213
func (d *DevAuth) RevokeToken(ctx context.Context, tokenID string) error {
11✔
1214
        l := log.FromContext(ctx)
11✔
1215
        tokenOID := oid.FromString(tokenID)
11✔
1216

11✔
1217
        var token *jwt.Token
11✔
1218
        token, err := d.db.GetToken(ctx, tokenOID)
11✔
1219
        if err != nil {
13✔
1220
                return err
2✔
1221
        }
2✔
1222

1223
        l.Warnf("Revoke token with jti: %s", tokenID)
9✔
1224
        err = d.db.DeleteToken(ctx, tokenOID)
9✔
1225

9✔
1226
        if err == nil && d.cache != nil {
13✔
1227
                err = d.cacheDeleteToken(ctx, token.Claims.Subject.String())
4✔
1228
                err = errors.Wrapf(
4✔
1229
                        err,
4✔
1230
                        "failed to delete token for %s from cache",
4✔
1231
                        token.Claims.Subject.String(),
4✔
1232
                )
4✔
1233
        }
4✔
1234
        return err
9✔
1235
}
1236

1237
func verifyTenantClaim(ctx context.Context, verifyTenant bool, tenant string) error {
27✔
1238

27✔
1239
        l := log.FromContext(ctx)
27✔
1240

27✔
1241
        if verifyTenant {
48✔
1242
                if tenant == "" {
21✔
1243
                        l.Errorf("No tenant claim in the token")
×
1244
                        return jwt.ErrTokenInvalid
×
1245
                }
×
1246
        } else if tenant != "" {
7✔
1247
                l.Errorf("Unexpected tenant claim: %s in the token", tenant)
×
1248
                return jwt.ErrTokenInvalid
×
1249
        }
×
1250

1251
        return nil
27✔
1252
}
1253

1254
func (d *DevAuth) VerifyToken(ctx context.Context, raw string) error {
33✔
1255

33✔
1256
        l := log.FromContext(ctx)
33✔
1257

33✔
1258
        token := &jwt.Token{}
33✔
1259

33✔
1260
        err := token.UnmarshalJWT([]byte(raw), d.jwt.FromJWT)
33✔
1261
        jti := token.Claims.ID
33✔
1262
        if err != nil {
38✔
1263
                if err == jwt.ErrTokenExpired && jti.String() != "" {
7✔
1264
                        l.Errorf("Token %s expired: %v", jti, err)
2✔
1265
                        return d.handleExpiredToken(ctx, jti)
2✔
1266
                }
2✔
1267
                l.Errorf("Token %s invalid: %v", jti, err)
3✔
1268
                return jwt.ErrTokenInvalid
3✔
1269
        }
1270

1271
        if !token.Claims.Device {
31✔
1272
                l.Errorf("not a device token")
2✔
1273
                return jwt.ErrTokenInvalid
2✔
1274
        }
2✔
1275

1276
        if err := verifyTenantClaim(ctx, d.verifyTenant, token.Claims.Tenant); err != nil {
27✔
1277
                return err
×
1278
        }
×
1279
        if err = d.checker.ValidateWithContext(ctx); err != nil {
28✔
1280
                return err
1✔
1281
        }
1✔
1282

1283
        origMethod := ctxhttpheader.FromContext(ctx, "X-Forwarded-Method")
27✔
1284
        origUri := ctxhttpheader.FromContext(ctx, "X-Forwarded-Uri")
27✔
1285
        origUri = purgeUriArgs(origUri)
27✔
1286

27✔
1287
        // throttle and try fetch token from cache - if cached, it was
27✔
1288
        // already verified against the db checks below, we trust it
27✔
1289
        cachedToken, err := d.cacheThrottleVerify(ctx, token, raw, origMethod, origUri)
27✔
1290

27✔
1291
        if err == cache.ErrTooManyRequests {
29✔
1292
                return err
2✔
1293
        }
2✔
1294

1295
        if cachedToken != "" {
29✔
1296
                // update device check-in time
4✔
1297
                if err := d.updateCheckInTime(
4✔
1298
                        ctx,
4✔
1299
                        token.Claims.Subject.String(),
4✔
1300
                        token.Claims.Tenant,
4✔
1301
                        nil,
4✔
1302
                ); err != nil {
4✔
1303
                        l.Errorf(
×
1304
                                "failed to update device check-in time for device %s: %s",
×
1305
                                token.Claims.Subject.String(), err.Error(),
×
1306
                        )
×
1307
                }
×
1308
                return nil
4✔
1309
        }
1310

1311
        // caching is best effort, don't fail
1312
        if err != nil {
33✔
1313
                l.Errorf("Failed to throttle for token %v: %s, continue.", token, err.Error())
12✔
1314
        }
12✔
1315

1316
        // cache check was a MISS, hit the db for verification
1317
        // check if token is in the system
1318
        _, err = d.db.GetToken(ctx, jti)
21✔
1319
        if err != nil {
24✔
1320
                if err == store.ErrTokenNotFound {
6✔
1321
                        l.Errorf("Token %s not found", jti)
3✔
1322
                        return err
3✔
1323
                }
3✔
1324
                return errors.Wrapf(err, "Cannot get token with id: %s from database: %s", jti, err)
×
1325
        }
1326

1327
        auth, err := d.db.GetAuthSetById(ctx, jti.String())
19✔
1328
        if err != nil {
19✔
1329
                if err == store.ErrAuthSetNotFound {
×
1330
                        l.Errorf("Auth set %s not found", jti)
×
1331
                        return err
×
1332
                }
×
1333
                return err
×
1334
        }
1335

1336
        if auth.Status != model.DevStatusAccepted {
21✔
1337
                return jwt.ErrTokenInvalid
2✔
1338
        }
2✔
1339

1340
        // reject authentication for device that is in the process of
1341
        // decommissioning
1342
        dev, err := d.db.GetDeviceById(ctx, auth.DeviceId)
17✔
1343
        if err != nil {
17✔
1344
                return err
×
1345
        }
×
1346
        if dev.Decommissioning {
19✔
1347
                l.Errorf("Token %s rejected, device %s is being decommissioned", jti, auth.DeviceId)
2✔
1348
                return jwt.ErrTokenInvalid
2✔
1349
        }
2✔
1350

1351
        // update device check-in time
1352
        if err := d.updateCheckInTime(
15✔
1353
                ctx,
15✔
1354
                token.Claims.Subject.String(),
15✔
1355
                token.Claims.Tenant,
15✔
1356
                dev.CheckInTime,
15✔
1357
        ); err != nil {
15✔
UNCOV
1358
                l.Errorf(
×
UNCOV
1359
                        "failed to update device check-in time for device %s: %s",
×
UNCOV
1360
                        token.Claims.Subject.String(), err.Error(),
×
UNCOV
1361
                )
×
UNCOV
1362
        }
×
1363

1364
        // after successful token verification - cache it (best effort)
1365
        _ = d.cacheSetToken(ctx, token, raw)
15✔
1366

15✔
1367
        return nil
15✔
1368
}
1369

1370
func (d *DevAuth) handleExpiredToken(ctx context.Context, jti oid.ObjectID) error {
2✔
1371
        err := d.db.DeleteToken(ctx, jti)
2✔
1372
        if err == store.ErrTokenNotFound {
2✔
1373
                l := log.FromContext(ctx)
×
1374
                l.Errorf("Token %s not found", jti)
×
1375
                return err
×
1376
        }
×
1377
        if err != nil {
2✔
1378
                return errors.Wrapf(err, "Cannot delete token with jti: %s : %s", jti, err)
×
1379
        }
×
1380
        return jwt.ErrTokenExpired
2✔
1381
}
1382

1383
// purgeUriArgs removes query string args from an uri string
1384
// important for burst control (bursts are per uri without args)
1385
func purgeUriArgs(uri string) string {
31✔
1386
        return strings.Split(uri, "?")[0]
31✔
1387
}
31✔
1388

1389
func (d *DevAuth) cacheThrottleVerify(
1390
        ctx context.Context,
1391
        token *jwt.Token,
1392
        originalRaw,
1393
        origMethod,
1394
        origUri string,
1395
) (string, error) {
27✔
1396
        if d.cache == nil {
34✔
1397
                return "", nil
7✔
1398
        }
7✔
1399

1400
        // try get cached/precomputed limits
1401
        limits, err := d.getApiLimits(ctx,
20✔
1402
                token.Claims.Tenant,
20✔
1403
                token.Claims.Subject.String())
20✔
1404

20✔
1405
        if err != nil {
28✔
1406
                return "", err
8✔
1407
        }
8✔
1408

1409
        // apply throttling and fetch cached token
1410
        cached, err := d.cache.Throttle(ctx,
12✔
1411
                originalRaw,
12✔
1412
                *limits,
12✔
1413
                token.Claims.Tenant,
12✔
1414
                token.Claims.Subject.String(),
12✔
1415
                cache.IdTypeDevice,
12✔
1416
                origUri,
12✔
1417
                origMethod)
12✔
1418

12✔
1419
        return cached, err
12✔
1420
}
1421

1422
func (d *DevAuth) cacheSetToken(ctx context.Context, token *jwt.Token, raw string) error {
15✔
1423
        if d.cache == nil {
18✔
1424
                return nil
3✔
1425
        }
3✔
1426

1427
        expireIn := time.Duration(token.Claims.ExpiresAt.Unix()-d.clock.Now().Unix()) * time.Second
12✔
1428

12✔
1429
        return d.cache.CacheToken(ctx,
12✔
1430
                token.Claims.Tenant,
12✔
1431
                token.Claims.Subject.String(),
12✔
1432
                cache.IdTypeDevice,
12✔
1433
                raw,
12✔
1434
                expireIn)
12✔
1435
}
1436

1437
func (d *DevAuth) getApiLimits(
1438
        ctx context.Context,
1439
        tid,
1440
        did string,
1441
) (*ratelimits.ApiLimits, error) {
20✔
1442
        limits, err := d.cache.GetLimits(ctx, tid, did, cache.IdTypeDevice)
20✔
1443
        if err != nil {
22✔
1444
                return nil, err
2✔
1445
        }
2✔
1446

1447
        if limits != nil {
28✔
1448
                return limits, nil
10✔
1449
        }
10✔
1450

1451
        dev, err := d.db.GetDeviceById(ctx, did)
8✔
1452
        if err != nil {
8✔
1453
                return nil, err
×
1454
        }
×
1455

1456
        t, err := d.cTenant.GetTenant(ctx, tid)
8✔
1457
        if err != nil {
12✔
1458
                return nil, errors.Wrap(err, "request to get tenant failed")
4✔
1459
        }
4✔
1460
        if t == nil {
4✔
1461
                return nil, errors.New("tenant not found")
×
1462
        }
×
1463

1464
        finalLimits := apiLimitsOverride(t.ApiLimits.DeviceLimits, dev.ApiLimits)
4✔
1465

4✔
1466
        err = d.cache.CacheLimits(ctx, finalLimits, tid, did, cache.IdTypeDevice)
4✔
1467

4✔
1468
        return &finalLimits, err
4✔
1469
}
1470

1471
func (d *DevAuth) cacheDeleteToken(ctx context.Context, did string) error {
71✔
1472
        if d.cache == nil {
118✔
1473
                return nil
47✔
1474
        }
47✔
1475

1476
        idData := identity.FromContext(ctx)
24✔
1477
        if idData == nil {
30✔
1478
                return errors.New("can't unpack tenant identity data from context")
6✔
1479
        }
6✔
1480
        tid := idData.Tenant
18✔
1481

18✔
1482
        return d.cache.DeleteToken(ctx, tid, did, cache.IdTypeDevice)
18✔
1483
}
1484

1485
// TODO move to 'ratelimits', as ApiLimits methods maybe?
1486
func apiLimitsOverride(src, dest ratelimits.ApiLimits) ratelimits.ApiLimits {
18✔
1487
        // override only if not default
18✔
1488
        if dest.ApiQuota.MaxCalls != 0 && dest.ApiQuota.IntervalSec != 0 {
22✔
1489
                src.ApiQuota.MaxCalls = dest.ApiQuota.MaxCalls
4✔
1490
                src.ApiQuota.IntervalSec = dest.ApiQuota.IntervalSec
4✔
1491
        }
4✔
1492

1493
        out := make([]ratelimits.ApiBurst, len(src.ApiBursts))
18✔
1494
        copy(out, src.ApiBursts)
18✔
1495

18✔
1496
        for _, bdest := range dest.ApiBursts {
28✔
1497
                found := false
10✔
1498
                for i, bsrc := range src.ApiBursts {
18✔
1499
                        if bdest.Action == bsrc.Action &&
8✔
1500
                                bdest.Uri == bsrc.Uri {
12✔
1501
                                out[i].MinIntervalSec = bdest.MinIntervalSec
4✔
1502
                                found = true
4✔
1503
                        }
4✔
1504
                }
1505

1506
                if !found {
16✔
1507
                        out = append(out,
6✔
1508
                                ratelimits.ApiBurst{
6✔
1509
                                        Action:         bdest.Action,
6✔
1510
                                        Uri:            bdest.Uri,
6✔
1511
                                        MinIntervalSec: bdest.MinIntervalSec},
6✔
1512
                        )
6✔
1513
                }
6✔
1514
        }
1515

1516
        src.ApiBursts = out
18✔
1517
        return src
18✔
1518
}
1519

1520
func (d *DevAuth) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
47✔
1521
        lim, err := d.db.GetLimit(ctx, name)
47✔
1522

47✔
1523
        switch err {
47✔
1524
        case nil:
35✔
1525
                return lim, nil
35✔
1526
        case store.ErrLimitNotFound:
5✔
1527
                return &model.Limit{Name: name, Value: 0}, nil
5✔
1528
        default:
8✔
1529
                return nil, err
8✔
1530
        }
1531
}
1532

1533
func (d *DevAuth) GetTenantLimit(
1534
        ctx context.Context,
1535
        name,
1536
        tenant_id string,
1537
) (*model.Limit, error) {
7✔
1538
        ctx = identity.WithContext(ctx, &identity.Identity{
7✔
1539
                Tenant: tenant_id,
7✔
1540
        })
7✔
1541

7✔
1542
        return d.GetLimit(ctx, name)
7✔
1543
}
7✔
1544

1545
// WithTenantVerification will force verification of tenant token with tenant
1546
// administrator when processing device authentication requests. Returns an
1547
// updated devauth.
1548
func (d *DevAuth) WithTenantVerification(c tenant.ClientRunner) *DevAuth {
49✔
1549
        d.cTenant = c
49✔
1550
        d.verifyTenant = true
49✔
1551
        return d
49✔
1552
}
49✔
1553

1554
func (d *DevAuth) WithCache(c cache.Cache) *DevAuth {
66✔
1555
        d.cache = c
66✔
1556
        return d
66✔
1557
}
66✔
1558

1559
func (d *DevAuth) WithClock(c utils.Clock) *DevAuth {
20✔
1560
        d.clock = c
20✔
1561
        return d
20✔
1562
}
20✔
1563

1564
func (d *DevAuth) SetTenantLimit(ctx context.Context, tenant_id string, limit model.Limit) error {
5✔
1565
        l := log.FromContext(ctx)
5✔
1566

5✔
1567
        ctx = identity.WithContext(ctx, &identity.Identity{
5✔
1568
                Tenant: tenant_id,
5✔
1569
        })
5✔
1570

5✔
1571
        l.Infof("setting limit %v for tenant %v", limit, tenant_id)
5✔
1572

5✔
1573
        if err := d.db.PutLimit(ctx, limit); err != nil {
7✔
1574
                l.Errorf("failed to save limit %v for tenant %v to database: %v",
2✔
1575
                        limit, tenant_id, err)
2✔
1576
                return errors.Wrapf(err, "failed to save limit %v for tenant %v to database",
2✔
1577
                        limit, tenant_id)
2✔
1578
        }
2✔
1579
        return nil
3✔
1580
}
1581

1582
func (d *DevAuth) DeleteTenantLimit(ctx context.Context, tenant_id string, limit string) error {
4✔
1583
        l := log.FromContext(ctx)
4✔
1584

4✔
1585
        ctx = identity.WithContext(ctx, &identity.Identity{
4✔
1586
                Tenant: tenant_id,
4✔
1587
        })
4✔
1588

4✔
1589
        l.Infof("removing limit %v for tenant %v", limit, tenant_id)
4✔
1590

4✔
1591
        if err := d.db.DeleteLimit(ctx, limit); err != nil {
6✔
1592
                l.Errorf("failed to delete limit %v for tenant %v to database: %v",
2✔
1593
                        limit, tenant_id, err)
2✔
1594
                return errors.Wrapf(err, "failed to delete limit %v for tenant %v to database",
2✔
1595
                        limit, tenant_id)
2✔
1596
        }
2✔
1597
        return nil
2✔
1598
}
1599

1600
func (d *DevAuth) GetDevCountByStatus(ctx context.Context, status string) (int, error) {
7✔
1601
        return d.db.GetDevCountByStatus(ctx, status)
7✔
1602
}
7✔
1603

1604
// canAcceptDevice checks if model.LimitMaxDeviceCount will be exceeded
1605
func (d *DevAuth) canAcceptDevice(ctx context.Context) (bool, error) {
33✔
1606
        limit, err := d.GetLimit(ctx, model.LimitMaxDeviceCount)
33✔
1607
        if err != nil {
37✔
1608
                return false, errors.Wrap(err, "can't get current device limit")
4✔
1609
        }
4✔
1610

1611
        if limit.Value == 0 {
40✔
1612
                return true, nil
11✔
1613
        }
11✔
1614

1615
        accepted, err := d.db.GetDevCountByStatus(ctx, model.DevStatusAccepted)
19✔
1616
        if err != nil {
21✔
1617
                return false, errors.Wrap(err, "can't get current device count")
2✔
1618
        }
2✔
1619

1620
        if uint64(accepted+1) <= limit.Value {
28✔
1621
                return true, nil
11✔
1622
        }
11✔
1623

1624
        return false, nil
7✔
1625
}
1626

1627
func (d *DevAuth) DeleteTokens(
1628
        ctx context.Context,
1629
        tenantID string,
1630
        deviceID string,
1631
) error {
13✔
1632
        var err error
13✔
1633
        ctx = identity.WithContext(ctx, &identity.Identity{
13✔
1634
                Tenant: tenantID,
13✔
1635
        })
13✔
1636

13✔
1637
        if deviceID != "" {
18✔
1638
                deviceOID := oid.FromString(deviceID)
5✔
1639
                if deviceOID.String() == "" {
5✔
1640
                        return ErrInvalidAuthSetID
×
1641
                }
×
1642
                err = d.db.DeleteTokenByDevId(ctx, deviceOID)
5✔
1643
        } else {
9✔
1644
                if err := d.cacheFlush(ctx); err != nil {
11✔
1645
                        return errors.Wrapf(
2✔
1646
                                err,
2✔
1647
                                "failed to flush cache when cleaning tokens for tenant %v",
2✔
1648
                                tenantID,
2✔
1649
                        )
2✔
1650
                }
2✔
1651

1652
                err = d.db.DeleteTokens(ctx)
7✔
1653
        }
1654

1655
        if err != nil && err != store.ErrTokenNotFound {
15✔
1656
                return errors.Wrapf(
4✔
1657
                        err,
4✔
1658
                        "failed to delete tokens for tenant: %v, device id: %v",
4✔
1659
                        tenantID,
4✔
1660
                        deviceID,
4✔
1661
                )
4✔
1662
        }
4✔
1663

1664
        return nil
7✔
1665
}
1666

1667
func (d *DevAuth) cacheFlush(ctx context.Context) error {
9✔
1668
        if d.cache == nil {
10✔
1669
                return nil
1✔
1670
        }
1✔
1671

1672
        return d.cache.FlushDB(ctx)
8✔
1673
}
1674

1675
func (d *DevAuth) ProvisionTenant(ctx context.Context, tenant_id string) error {
5✔
1676
        tenantCtx := identity.WithContext(ctx, &identity.Identity{
5✔
1677
                Tenant: tenant_id,
5✔
1678
        })
5✔
1679

5✔
1680
        dbname := mstore.DbFromContext(tenantCtx, mongo.DbName)
5✔
1681

5✔
1682
        return d.db.WithAutomigrate().MigrateTenant(tenantCtx, dbname, mongo.DbVersion)
5✔
1683
}
5✔
1684

1685
func (d *DevAuth) GetTenantDeviceStatus(
1686
        ctx context.Context,
1687
        tenantId,
1688
        deviceId string,
1689
) (*model.Status, error) {
6✔
1690
        if tenantId != "" {
12✔
1691
                ctx = identity.WithContext(ctx, &identity.Identity{
6✔
1692
                        Tenant: tenantId,
6✔
1693
                })
6✔
1694
        }
6✔
1695
        dev, err := d.db.GetDeviceById(ctx, deviceId)
6✔
1696
        switch err {
6✔
1697
        case nil:
2✔
1698
                return &model.Status{Status: dev.Status}, nil
2✔
1699
        case store.ErrDevNotFound:
2✔
1700
                return nil, ErrDeviceNotFound
2✔
1701
        default:
2✔
1702
                return nil, errors.Wrapf(err, "get device %s failed", deviceId)
2✔
1703

1704
        }
1705
}
1706

1707
func (d *DevAuth) updateCheckInTime(
1708
        ctx context.Context,
1709
        deviceId string,
1710
        tenantId string,
1711
        previous *time.Time,
1712
) error {
29✔
1713
        var err error
29✔
1714
        checkInTime := uto.TimePtr(time.Now().UTC())
29✔
1715
        // in case cache is disabled, use mongo
29✔
1716
        if d.cache == nil {
42✔
1717
                if err = d.db.UpdateDevice(ctx,
13✔
1718
                        deviceId,
13✔
1719
                        model.DeviceUpdate{
13✔
1720
                                CheckInTime: checkInTime,
13✔
1721
                        }); err != nil {
13✔
1722
                        return err
×
1723
                }
×
1724
        } else {
16✔
1725
                // get check-in time from cache
16✔
1726
                previous, err = d.cache.GetCheckInTime(ctx, tenantId, deviceId)
16✔
1727
                if err != nil {
16✔
1728
                        return err
×
1729
                }
×
1730
                // update check-in time in cache
1731
                err = d.cache.CacheCheckInTime(ctx, checkInTime, tenantId, deviceId)
16✔
1732
                if err != nil {
16✔
1733
                        return err
×
1734
                }
×
1735
        }
1736
        // compare data without a time of current and previous check-in time
1737
        // and if it's different trigger reindexing (if enabled)
1738
        // and save check-in time in the database
1739
        if previous == nil ||
29✔
1740
                (previous != nil &&
29✔
1741
                        !previous.Truncate(24*time.Hour).Equal(checkInTime.Truncate(24*time.Hour))) {
58✔
1742
                // trigger reindexing
29✔
1743
                if d.config.EnableReporting {
30✔
1744
                        if err = d.cOrch.SubmitReindexReporting(ctx, deviceId); err != nil {
1✔
UNCOV
1745
                                return errors.Wrap(err, "reindex reporting job error")
×
UNCOV
1746
                        }
×
1747
                }
1748
                // dump cached value to database
1749
                if d.cache != nil {
45✔
1750
                        if err = d.db.UpdateDevice(ctx,
16✔
1751
                                deviceId,
16✔
1752
                                model.DeviceUpdate{
16✔
1753
                                        CheckInTime: checkInTime,
16✔
1754
                                }); err != nil {
16✔
1755
                                return err
×
1756
                        }
×
1757
                }
1758
        }
1759
        return nil
29✔
1760
}
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