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

mendersoftware / deviceauth / 857655394

pending completion
857655394

Pull #644

gitlab-ci

Krzysztof Jaskiewicz
chore: reduce cyclomatic complexity of VerifyToken method
Pull Request #644: feat: handle device check-in time

106 of 160 new or added lines in 3 files covered. (66.25%)

99 existing lines in 3 files now uncovered.

4627 of 5519 relevant lines covered (83.84%)

46.04 hits per line

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

87.79
/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 {
295✔
160
        // initialize checker using an empty merge (returns nil on validate)
295✔
161
        checker := access.Merge()
295✔
162
        if config.HaveAddons {
296✔
163
                checker = access.NewAddonChecker()
1✔
164
        }
1✔
165

166
        return &DevAuth{
295✔
167
                db:           d,
295✔
168
                invClient:    inventory.NewClient(config.InventoryAddr, false),
295✔
169
                cOrch:        co,
295✔
170
                jwt:          jwt,
295✔
171
                clientGetter: simpleApiClientGetter,
295✔
172
                verifyTenant: false,
295✔
173
                config:       config,
295✔
174
                clock:        utils.NewClock(),
295✔
175
                checker:      checker,
295✔
176
        }
295✔
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
                return string(raw), nil
11✔
472
        }
473

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

477
}
478

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

633
        return nil
55✔
634
}
635

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

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

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

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

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

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

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

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

687
        return areq, nil
17✔
688
}
689

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

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

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

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

733
        return devs, err
1✔
734
}
735

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

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

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

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

769
        return dev, err
5✔
770
}
771

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

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

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

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

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

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

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

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

813
        return err
5✔
814
}
815

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

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

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

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

847
        return nil
3✔
848
}
849

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

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

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

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

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

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

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

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

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

923
                return nil
×
924
        }
925

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

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

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

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

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

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

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

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

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

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

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

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

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

1001
        return nil
7✔
1002
}
1003

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

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

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

1026
        currentStatus := aset.Status
29✔
1027

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1250
        return nil
27✔
1251
}
1252

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

15✔
1366
        return nil
15✔
1367
}
1368

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1623
        return false, nil
7✔
1624
}
1625

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

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

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

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

1663
        return nil
7✔
1664
}
1665

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

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

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

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

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

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

1703
        }
1704
}
1705

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