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

mendersoftware / deviceauth / 1290890656

14 May 2024 02:46PM UTC coverage: 81.686% (+0.03%) from 81.658%
1290890656

push

gitlab-ci

web-flow
Merge pull request #714 from alfrunes/MEN-7241

fix: Preauthorize force behavior applies to existing auth sets

36 of 40 new or added lines in 2 files covered. (90.0%)

2 existing lines in 1 file now uncovered.

4835 of 5919 relevant lines covered (81.69%)

50.55 hits per line

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

87.66
/devauth/devauth.go
1
// Copyright 2024 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
        "strings"
21
        "time"
22

23
        "github.com/mendersoftware/go-lib-micro/addons"
24
        ctxhttpheader "github.com/mendersoftware/go-lib-micro/context/httpheader"
25
        "github.com/mendersoftware/go-lib-micro/identity"
26
        "github.com/mendersoftware/go-lib-micro/log"
27
        "github.com/mendersoftware/go-lib-micro/mongo/oid"
28
        "github.com/mendersoftware/go-lib-micro/plan"
29
        "github.com/mendersoftware/go-lib-micro/ratelimits"
30
        "github.com/mendersoftware/go-lib-micro/requestid"
31
        "github.com/pkg/errors"
32

33
        "github.com/mendersoftware/deviceauth/access"
34
        "github.com/mendersoftware/deviceauth/cache"
35
        "github.com/mendersoftware/deviceauth/client/inventory"
36
        "github.com/mendersoftware/deviceauth/client/orchestrator"
37
        "github.com/mendersoftware/deviceauth/client/tenant"
38
        "github.com/mendersoftware/deviceauth/jwt"
39
        "github.com/mendersoftware/deviceauth/model"
40
        "github.com/mendersoftware/deviceauth/store"
41
        "github.com/mendersoftware/deviceauth/utils"
42
        uto "github.com/mendersoftware/deviceauth/utils/to"
43
)
44

45
const (
46
        MsgErrDevAuthUnauthorized = "dev auth: unauthorized"
47
        MsgErrDevAuthBadRequest   = "dev auth: bad request"
48
        InventoryScopeSystem      = "system"
49
)
50

51
var (
52
        ErrDevAuthUnauthorized   = errors.New(MsgErrDevAuthUnauthorized)
53
        ErrDevIdAuthIdMismatch   = errors.New("dev auth: dev ID and auth ID mismatch")
54
        ErrMaxDeviceCountReached = errors.New("maximum number of accepted devices reached")
55
        ErrDeviceExists          = errors.New("device already exists")
56
        ErrDeviceNotFound        = errors.New("device not found")
57
        ErrDevAuthBadRequest     = errors.New(MsgErrDevAuthBadRequest)
58

59
        ErrInvalidDeviceID  = errors.New("invalid device ID type")
60
        ErrInvalidAuthSetID = errors.New("auth set id is not a valid ID")
61
)
62

63
func IsErrDevAuthUnauthorized(e error) bool {
1✔
64
        return strings.HasPrefix(e.Error(), MsgErrDevAuthUnauthorized)
1✔
65
}
1✔
66

67
func MakeErrDevAuthUnauthorized(e error) error {
5✔
68
        return errors.Wrap(e, MsgErrDevAuthUnauthorized)
5✔
69
}
5✔
70

71
func IsErrDevAuthBadRequest(e error) bool {
×
72
        return strings.HasPrefix(e.Error(), MsgErrDevAuthBadRequest)
×
73
}
×
74

75
func MakeErrDevAuthBadRequest(e error) error {
2✔
76
        return errors.Wrap(e, MsgErrDevAuthBadRequest)
2✔
77
}
2✔
78

79
// this device auth service interface
80
//
81
//go:generate ../utils/mockgen.sh
82
type App interface {
83
        HealthCheck(ctx context.Context) error
84
        SubmitAuthRequest(ctx context.Context, r *model.AuthReq) (string, error)
85

86
        GetDevices(
87
                ctx context.Context,
88
                skip,
89
                limit uint,
90
                filter model.DeviceFilter,
91
        ) ([]model.Device, error)
92
        GetDevice(ctx context.Context, dev_id string) (*model.Device, error)
93
        DecommissionDevice(ctx context.Context, dev_id string) error
94
        DeleteDevice(ctx context.Context, dev_id string) error
95
        DeleteAuthSet(ctx context.Context, dev_id string, auth_id string) error
96
        AcceptDeviceAuth(ctx context.Context, dev_id string, auth_id string) error
97
        RejectDeviceAuth(ctx context.Context, dev_id string, auth_id string) error
98
        ResetDeviceAuth(ctx context.Context, dev_id string, auth_id string) error
99
        PreauthorizeDevice(ctx context.Context, req *model.PreAuthReq) (*model.Device, error)
100

101
        RevokeToken(ctx context.Context, tokenID string) error
102
        VerifyToken(ctx context.Context, token string) error
103
        DeleteTokens(ctx context.Context, tenantID, deviceID string) error
104

105
        SetTenantLimit(ctx context.Context, tenant_id string, limit model.Limit) error
106
        DeleteTenantLimit(ctx context.Context, tenant_id string, limit string) error
107

108
        GetLimit(ctx context.Context, name string) (*model.Limit, error)
109
        GetTenantLimit(ctx context.Context, name, tenant_id string) (*model.Limit, error)
110

111
        GetDevCountByStatus(ctx context.Context, status string) (int, error)
112

113
        GetTenantDeviceStatus(ctx context.Context, tenantId, deviceId string) (*model.Status, error)
114
}
115

116
type DevAuth struct {
117
        db           store.DataStore
118
        invClient    inventory.Client
119
        cOrch        orchestrator.ClientRunner
120
        cTenant      tenant.ClientRunner
121
        jwt          jwt.Handler
122
        jwtFallback  jwt.Handler
123
        verifyTenant bool
124
        config       Config
125
        cache        cache.Cache
126
        clock        utils.Clock
127
        checker      access.Checker
128
}
129

130
type Config struct {
131
        // token issuer
132
        Issuer string
133
        // token expiration time
134
        ExpirationTime int64
135
        // Default tenant token to use when the client supplies none. Can be
136
        // empty
137
        DefaultTenantToken string
138
        InventoryAddr      string
139

140
        EnableReporting bool
141
        HaveAddons      bool
142
}
143

144
func NewDevAuth(d store.DataStore, co orchestrator.ClientRunner,
145
        jwt jwt.Handler, config Config) *DevAuth {
150✔
146
        // initialize checker using an empty merge (returns nil on validate)
150✔
147
        checker := access.Merge()
150✔
148
        if config.HaveAddons {
150✔
149
                checker = access.NewAddonChecker()
×
150
        }
×
151

152
        return &DevAuth{
150✔
153
                db:           d,
150✔
154
                invClient:    inventory.NewClient(config.InventoryAddr, false),
150✔
155
                cOrch:        co,
150✔
156
                jwt:          jwt,
150✔
157
                verifyTenant: false,
150✔
158
                config:       config,
150✔
159
                clock:        utils.NewClock(),
150✔
160
                checker:      checker,
150✔
161
        }
150✔
162
}
163

164
func (d *DevAuth) HealthCheck(ctx context.Context) error {
6✔
165
        err := d.db.Ping(ctx)
6✔
166
        if err != nil {
7✔
167
                return errors.Wrap(err, "error reaching MongoDB")
1✔
168
        }
1✔
169
        err = d.invClient.CheckHealth(ctx)
5✔
170
        if err != nil {
6✔
171
                return errors.Wrap(err, "Inventory service unhealthy")
1✔
172
        }
1✔
173
        err = d.cOrch.CheckHealth(ctx)
4✔
174
        if err != nil {
5✔
175
                return errors.Wrap(err, "Workflows service unhealthy")
1✔
176
        }
1✔
177
        if d.verifyTenant {
5✔
178
                err = d.cTenant.CheckHealth(ctx)
2✔
179
                if err != nil {
3✔
180
                        return errors.Wrap(err, "Tenantadm service unhealthy")
1✔
181
                }
1✔
182
        }
183
        return nil
2✔
184
}
185

186
func (d *DevAuth) setDeviceIdentity(ctx context.Context, dev *model.Device, tenantId string) error {
4✔
187
        attributes := make([]model.DeviceAttribute, len(dev.IdDataStruct))
4✔
188
        i := 0
4✔
189
        for name, value := range dev.IdDataStruct {
8✔
190
                if name == "status" {
4✔
191
                        //we have to forbid the client to override attribute status in identity scope
×
192
                        //since it stands for status of a device (as in: accepted, rejected, preauthorized)
×
193
                        continue
×
194
                }
195
                attribute := model.DeviceAttribute{
4✔
196
                        Name:  name,
4✔
197
                        Value: value,
4✔
198
                        Scope: "identity",
4✔
199
                }
4✔
200
                attributes[i] = attribute
4✔
201
                i++
4✔
202
        }
203
        attrJson, err := json.Marshal(attributes)
4✔
204
        if err != nil {
4✔
205
                return errors.New("internal error: cannot marshal attributes into json")
×
206
        }
×
207
        if err := d.cOrch.SubmitUpdateDeviceInventoryJob(
4✔
208
                ctx,
4✔
209
                orchestrator.UpdateDeviceInventoryReq{
4✔
210
                        RequestId:  requestid.FromContext(ctx),
4✔
211
                        TenantId:   tenantId,
4✔
212
                        DeviceId:   dev.Id,
4✔
213
                        Scope:      "identity",
4✔
214
                        Attributes: string(attrJson),
4✔
215
                }); err != nil {
4✔
216
                return errors.Wrap(err, "failed to start device inventory update job")
×
217
        }
×
218
        if d.config.EnableReporting {
5✔
219
                if err := d.cOrch.SubmitReindexReporting(ctx, string(dev.Id)); err != nil {
1✔
220
                        return errors.Wrap(err, "reindex reporting job error")
×
221
                }
×
222
        }
223
        return nil
4✔
224
}
225

226
func (d *DevAuth) getDeviceFromAuthRequest(
227
        ctx context.Context,
228
        r *model.AuthReq,
229
) (*model.Device, error) {
13✔
230
        dev := model.NewDevice("", r.IdData, r.PubKey)
13✔
231

13✔
232
        l := log.FromContext(ctx)
13✔
233

13✔
234
        idDataStruct, idDataSha256, err := parseIdData(r.IdData)
13✔
235
        if err != nil {
13✔
236
                return nil, MakeErrDevAuthBadRequest(err)
×
237
        }
×
238

239
        dev.IdDataStruct = idDataStruct
13✔
240
        dev.IdDataSha256 = idDataSha256
13✔
241

13✔
242
        // record device
13✔
243
        err = d.db.AddDevice(ctx, *dev)
13✔
244
        addDeviceErr := err
13✔
245
        if err != nil && err != store.ErrObjectExists {
14✔
246
                l.Errorf("failed to add/find device: %v", err)
1✔
247
                return nil, err
1✔
248
        }
1✔
249

250
        // either the device was added or it was already present, in any case,
251
        // pull it from DB
252
        dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
12✔
253
        if err != nil {
13✔
254
                l.Error("failed to find device but could not add either")
1✔
255
                return nil, errors.New("failed to locate device")
1✔
256
        }
1✔
257

258
        idData := identity.FromContext(ctx)
11✔
259
        tenantId := ""
11✔
260
        if idData != nil {
16✔
261
                tenantId = idData.Tenant
5✔
262
        }
5✔
263
        if addDeviceErr != store.ErrObjectExists {
13✔
264
                if err := d.setDeviceIdentity(ctx, dev, tenantId); err != nil {
2✔
265
                        return nil, err
×
266
                }
×
267
        }
268

269
        // check if the device is in the decommissioning state
270
        if dev.Decommissioning {
11✔
271
                l.Warnf("Device %s in the decommissioning state.", dev.Id)
×
272
                return nil, ErrDevAuthUnauthorized
×
273
        }
×
274

275
        return dev, nil
11✔
276
}
277

278
func (d *DevAuth) signToken(ctx context.Context) jwt.SignFunc {
6✔
279
        return func(t *jwt.Token) (string, error) {
12✔
280
                return d.jwt.ToJWT(t)
6✔
281
        }
6✔
282
}
283

284
func (d *DevAuth) doVerifyTenant(ctx context.Context, token string) (*tenant.Tenant, error) {
10✔
285
        t, err := d.cTenant.VerifyToken(ctx, token)
10✔
286

10✔
287
        if err != nil {
15✔
288
                if tenant.IsErrTokenVerificationFailed(err) {
8✔
289
                        return nil, MakeErrDevAuthUnauthorized(err)
3✔
290
                }
3✔
291

292
                return nil, errors.Wrap(err, "request to verify tenant token failed")
2✔
293
        }
294

295
        return t, nil
5✔
296
}
297

298
func (d *DevAuth) getTenantWithDefault(
299
        ctx context.Context,
300
        tenantToken,
301
        defaultToken string,
302
) (context.Context, *tenant.Tenant, error) {
9✔
303
        l := log.FromContext(ctx)
9✔
304

9✔
305
        if tenantToken == "" && defaultToken == "" {
10✔
306
                return nil, nil, MakeErrDevAuthUnauthorized(errors.New("tenant token missing"))
1✔
307
        }
1✔
308

309
        var t *tenant.Tenant
8✔
310
        var err error
8✔
311

8✔
312
        // try the provided token
8✔
313
        // but continue on errors and maybe try the default token
8✔
314
        if tenantToken != "" {
15✔
315
                t, err = d.doVerifyTenant(ctx, tenantToken)
7✔
316

7✔
317
                if err != nil {
11✔
318
                        l.Errorf("Failed to verify supplied tenant token: %s", err.Error())
4✔
319
                }
4✔
320
        }
321

322
        // if we still haven't selected a tenant - the token didn't work
323
        // try the default one
324
        if t == nil && defaultToken != "" {
11✔
325
                t, err = d.doVerifyTenant(ctx, defaultToken)
3✔
326
                if err != nil {
4✔
327
                        l.Errorf("Failed to verify default tenant token: %s", err.Error())
1✔
328
                }
1✔
329
        }
330

331
        // none of the tokens worked
332
        if err != nil {
11✔
333
                if tenant.IsErrTokenVerificationFailed(err) {
3✔
334
                        return ctx, nil, MakeErrDevAuthUnauthorized(err)
×
335
                }
×
336
                return ctx, nil, err
3✔
337
        }
338

339
        tCtx := identity.WithContext(ctx, &identity.Identity{
5✔
340
                Subject: "internal",
5✔
341
                Tenant:  t.ID,
5✔
342
        })
5✔
343

5✔
344
        return tCtx, t, nil
5✔
345
}
346

347
func (d *DevAuth) SubmitAuthRequest(ctx context.Context, r *model.AuthReq) (string, error) {
26✔
348
        l := log.FromContext(ctx)
26✔
349

26✔
350
        var tenant *tenant.Tenant
26✔
351
        var err error
26✔
352

26✔
353
        if d.verifyTenant {
35✔
354
                ctx, tenant, err = d.getTenantWithDefault(ctx, r.TenantToken, d.config.DefaultTenantToken)
9✔
355
                if err != nil {
13✔
356
                        return "", err
4✔
357
                }
4✔
358
        } else {
17✔
359
                // ignore identity data when tenant verification is off
17✔
360
                // it's possible that the device will provide old auth token or old tenant token
17✔
361
                // in the authorization header;
17✔
362
                // in that case we need to wipe identity data from the context
17✔
363
                ctx = identity.WithContext(ctx, nil)
17✔
364
        }
17✔
365

366
        // first, try to handle preauthorization
367
        authSet, err := d.processPreAuthRequest(ctx, r)
22✔
368
        if err != nil {
29✔
369
                return "", err
7✔
370
        }
7✔
371

372
        // if not a preauth request, process with regular auth request handling
373
        if authSet == nil {
28✔
374
                authSet, err = d.processAuthRequest(ctx, r)
13✔
375
                if err != nil {
16✔
376
                        return "", err
3✔
377
                }
3✔
378
        }
379

380
        // request was already present in DB, check its status
381
        if authSet.Status == model.DevStatusAccepted {
18✔
382
                jti := oid.FromString(authSet.Id)
6✔
383
                if jti.String() == "" {
6✔
384
                        return "", ErrInvalidAuthSetID
×
385
                }
×
386
                sub := oid.FromString(authSet.DeviceId)
6✔
387
                if sub.String() == "" {
6✔
388
                        return "", ErrInvalidDeviceID
×
389
                }
×
390
                now := time.Now()
6✔
391
                token := &jwt.Token{Claims: jwt.Claims{
6✔
392
                        ID:      jti,
6✔
393
                        Subject: sub,
6✔
394
                        Issuer:  d.config.Issuer,
6✔
395
                        ExpiresAt: jwt.Time{
6✔
396
                                Time: now.Add(time.Second *
6✔
397
                                        time.Duration(d.config.ExpirationTime)),
6✔
398
                        },
6✔
399
                        IssuedAt: jwt.Time{Time: now},
6✔
400
                        Device:   true,
6✔
401
                }}
6✔
402

6✔
403
                if d.verifyTenant {
8✔
404
                        token.Claims.Tenant = tenant.ID
2✔
405
                        token.Claims.Plan = tenant.Plan
2✔
406
                        token.Claims.Addons = tenant.Addons
2✔
407
                        token.Claims.Trial = tenant.Trial
2✔
408
                } else {
6✔
409
                        token.Claims.Plan = plan.PlanEnterprise
4✔
410
                        token.Addons = addons.AllAddonsEnabled
4✔
411
                }
4✔
412

413
                // sign and encode as JWT
414
                raw, err := token.MarshalJWT(d.signToken(ctx))
6✔
415
                if err != nil {
6✔
416
                        return "", errors.Wrap(err, "generate token error")
×
417
                }
×
418

419
                if err := d.db.AddToken(ctx, token); err != nil {
6✔
420
                        return "", errors.Wrap(err, "add token error")
×
421
                }
×
422

423
                l.Infof("Token %s assigned to device %s",
6✔
424
                        token.Claims.ID, token.Claims.Subject)
6✔
425
                d.updateCheckInTime(ctx, authSet.DeviceId, token.Claims.Tenant, nil)
6✔
426
                return string(raw), nil
6✔
427
        }
428

429
        // no token, return device unauthorized
430
        return "", ErrDevAuthUnauthorized
7✔
431

432
}
433

434
func (d *DevAuth) handlePreAuthDevice(
435
        ctx context.Context,
436
        aset *model.AuthSet,
437
) (*model.AuthSet, error) {
7✔
438
        var deviceAlreadyAccepted bool
7✔
439
        // check the device status
7✔
440
        // if the device status is accepted then do not trigger provisioning workflow
7✔
441
        // this needs to be checked before changing authentication set status
7✔
442
        dev, err := d.db.GetDeviceById(ctx, aset.DeviceId)
7✔
443
        if err != nil {
8✔
444
                return nil, err
1✔
445
        }
1✔
446

447
        // check if the device is in the decommissioning state
448
        if dev.Decommissioning {
7✔
449
                l := log.FromContext(ctx)
1✔
450
                l.Warnf("Device %s in the decommissioning state.", dev.Id)
1✔
451
                return nil, ErrDevAuthUnauthorized
1✔
452
        }
1✔
453

454
        currentStatus := dev.Status
5✔
455
        if dev.Status != model.DevStatusAccepted {
9✔
456
                // auth set is ok for auto-accepting, check device limit
4✔
457
                allow, err := d.canAcceptDevice(ctx)
4✔
458
                if err != nil {
5✔
459
                        return nil, err
1✔
460
                }
1✔
461

462
                if !allow {
4✔
463
                        return nil, ErrMaxDeviceCountReached
1✔
464
                }
1✔
465
        }
466

467
        // Ensure that the old acceptable auth sets are rejected
468
        if err := d.db.RejectAuthSetsForDevice(ctx, aset.DeviceId); err != nil &&
3✔
469
                !errors.Is(err, store.ErrAuthSetNotFound) {
3✔
470
                return nil, errors.Wrap(err, "failed to reject auth sets")
×
471
        }
×
472
        update := model.AuthSetUpdate{
3✔
473
                Status: model.DevStatusAccepted,
3✔
474
        }
3✔
475
        // persist the 'accepted' status in both auth set, and device
3✔
476
        if err := d.db.UpdateAuthSetById(ctx, aset.Id, update); err != nil {
3✔
477
                return nil, errors.Wrap(err, "failed to update auth set status")
×
478
        }
×
479

480
        if err := d.updateDeviceStatus(
3✔
481
                ctx,
3✔
482
                aset.DeviceId,
3✔
483
                model.DevStatusAccepted,
3✔
484
                currentStatus,
3✔
485
        ); err != nil {
3✔
486
                return nil, err
×
487
        }
×
488

489
        aset.Status = model.DevStatusAccepted
3✔
490
        dev.Status = model.DevStatusAccepted
3✔
491
        dev.AuthSets = append(dev.AuthSets, *aset)
3✔
492

3✔
493
        if !deviceAlreadyAccepted {
6✔
494
                reqId := requestid.FromContext(ctx)
3✔
495
                var tenantID string
3✔
496
                if idty := identity.FromContext(ctx); idty != nil {
3✔
497
                        tenantID = idty.Tenant
×
498
                }
×
499

500
                // submit device accepted job
501
                if err := d.cOrch.SubmitProvisionDeviceJob(
3✔
502
                        ctx,
3✔
503
                        orchestrator.ProvisionDeviceReq{
3✔
504
                                RequestId: reqId,
3✔
505
                                DeviceID:  aset.DeviceId,
3✔
506
                                TenantID:  tenantID,
3✔
507
                                Device:    dev,
3✔
508
                                Status:    dev.Status,
3✔
509
                        }); err != nil {
4✔
510
                        return nil, errors.Wrap(err, "submit device provisioning job error")
1✔
511
                }
1✔
512
        }
513
        return aset, nil
2✔
514
}
515

516
func (d *DevAuth) processPreAuthRequest(
517
        ctx context.Context,
518
        r *model.AuthReq,
519
) (*model.AuthSet, error) {
22✔
520

22✔
521
        _, idDataSha256, err := parseIdData(r.IdData)
22✔
522
        if err != nil {
23✔
523
                return nil, MakeErrDevAuthBadRequest(err)
1✔
524
        }
1✔
525

526
        // authset exists?
527
        aset, err := d.db.GetAuthSetByIdDataHashKeyByStatus(
21✔
528
                ctx,
21✔
529
                idDataSha256,
21✔
530
                r.PubKey,
21✔
531
                model.DevStatusPreauth,
21✔
532
        )
21✔
533
        switch err {
21✔
534
        case nil:
18✔
535
                break
18✔
536
        case store.ErrAuthSetNotFound:
2✔
537
                return nil, nil
2✔
538
        default:
1✔
539
                return nil, errors.Wrap(err, "failed to fetch auth set")
1✔
540
        }
541

542
        // if authset status is not 'preauthorized', nothing to do
543
        if aset.Status != model.DevStatusPreauth {
29✔
544
                return nil, nil
11✔
545
        }
11✔
546
        return d.handlePreAuthDevice(ctx, aset)
7✔
547
}
548

549
func (d *DevAuth) updateDeviceStatus(
550
        ctx context.Context,
551
        devId,
552
        status string,
553
        currentStatus string,
554
) error {
34✔
555
        newStatus, err := d.db.GetDeviceStatus(ctx, devId)
34✔
556
        if currentStatus == newStatus {
37✔
557
                return nil
3✔
558
        }
3✔
559
        if status == "" {
58✔
560
                switch err {
26✔
561
                case nil:
24✔
562
                        status = newStatus
24✔
563
                case store.ErrAuthSetNotFound:
2✔
564
                        status = model.DevStatusNoAuth
2✔
565
                default:
1✔
566
                        return errors.Wrap(err, "Cannot determine device status")
1✔
567
                }
568
        }
569

570
        // submit device status change job
571
        dev, err := d.db.GetDeviceById(ctx, devId)
31✔
572
        if err != nil {
31✔
573
                return errors.Wrap(err, "db get device by id error")
×
574
        }
×
575

576
        tenantId := ""
31✔
577
        idData := identity.FromContext(ctx)
31✔
578
        if idData != nil {
38✔
579
                tenantId = idData.Tenant
7✔
580
        }
7✔
581
        req := orchestrator.UpdateDeviceStatusReq{
31✔
582
                RequestId: requestid.FromContext(ctx),
31✔
583
                Devices: []model.DeviceInventoryUpdate{{
31✔
584
                        Id:       dev.Id,
31✔
585
                        Revision: dev.Revision + 1,
31✔
586
                }},
31✔
587
                TenantId: tenantId,
31✔
588
                Status:   status,
31✔
589
        }
31✔
590
        if err := d.cOrch.SubmitUpdateDeviceStatusJob(ctx, req); err != nil {
32✔
591
                return errors.Wrap(err, "update device status job error")
1✔
592
        }
1✔
593

594
        if err := d.db.UpdateDevice(ctx,
31✔
595
                devId,
31✔
596
                model.DeviceUpdate{
31✔
597
                        Status:    status,
31✔
598
                        UpdatedTs: uto.TimePtr(time.Now().UTC()),
31✔
599
                }); err != nil {
32✔
600
                return errors.Wrap(err, "failed to update device status")
1✔
601
        }
1✔
602

603
        if d.config.EnableReporting {
32✔
604
                if err := d.cOrch.SubmitReindexReporting(ctx, devId); err != nil {
3✔
605
                        return errors.Wrap(err, "reindex reporting job error")
1✔
606
                }
1✔
607
        }
608

609
        return nil
29✔
610
}
611

612
// processAuthRequest will process incoming auth request and record authentication
613
// data information it contains. Returns a tupe (auth set, error). If no errors were
614
// present, model.AuthSet.Status will indicate the status of device admission
615
func (d *DevAuth) processAuthRequest(
616
        ctx context.Context,
617
        r *model.AuthReq,
618
) (*model.AuthSet, error) {
13✔
619

13✔
620
        l := log.FromContext(ctx)
13✔
621

13✔
622
        // get device associated with given authorization request
13✔
623
        dev, err := d.getDeviceFromAuthRequest(ctx, r)
13✔
624
        if err != nil {
15✔
625
                return nil, err
2✔
626
        }
2✔
627

628
        idDataStruct, idDataSha256, err := parseIdData(r.IdData)
11✔
629
        if err != nil {
11✔
630
                return nil, MakeErrDevAuthBadRequest(err)
×
631
        }
×
632

633
        areq := &model.AuthSet{
11✔
634
                Id:           oid.NewUUIDv4().String(),
11✔
635
                IdData:       r.IdData,
11✔
636
                IdDataStruct: idDataStruct,
11✔
637
                IdDataSha256: idDataSha256,
11✔
638
                PubKey:       r.PubKey,
11✔
639
                DeviceId:     dev.Id,
11✔
640
                Status:       model.DevStatusPending,
11✔
641
                Timestamp:    uto.TimePtr(time.Now()),
11✔
642
        }
11✔
643

11✔
644
        // record authentication request
11✔
645
        err = d.db.AddAuthSet(ctx, *areq)
11✔
646
        if err != nil && err != store.ErrObjectExists {
11✔
647
                return nil, err
×
648
        }
×
649

650
        // update the device status
651
        if err := d.updateDeviceStatus(ctx, dev.Id, "", dev.Status); err != nil {
11✔
652
                return nil, err
×
653
        }
×
654

655
        // either the request was added or it was already present in the DB, get
656
        // it now
657
        areq, err = d.db.GetAuthSetByIdDataHashKey(ctx, idDataSha256, r.PubKey)
11✔
658
        if err != nil {
12✔
659
                l.Error("failed to find device auth set but could not add one either")
1✔
660
                return nil, errors.New("failed to locate device auth set")
1✔
661
        }
1✔
662

663
        return areq, nil
10✔
664
}
665

666
func (d *DevAuth) GetDevices(
667
        ctx context.Context,
668
        skip,
669
        limit uint,
670
        filter model.DeviceFilter,
671
) ([]model.Device, error) {
3✔
672
        devs, err := d.db.GetDevices(ctx, skip, limit, filter)
3✔
673
        if err != nil {
3✔
674
                return nil, errors.Wrap(err, "failed to list devices")
×
675
        }
×
676

677
        for i := range devs {
6✔
678
                devs[i].AuthSets, err = d.db.GetAuthSetsForDevice(ctx, devs[i].Id)
3✔
679
                if err != nil && err != store.ErrAuthSetNotFound {
3✔
680
                        return nil, errors.Wrap(err, "db get auth sets error")
×
681
                }
×
682
        }
683

684
        // update check-in time
685
        if d.cache != nil {
5✔
686
                tenantID := ""
2✔
687
                idData := identity.FromContext(ctx)
2✔
688
                if idData != nil {
4✔
689
                        tenantID = idData.Tenant
2✔
690
                }
2✔
691

692
                ids := make([]string, len(devs))
2✔
693
                for i := range devs {
4✔
694
                        ids[i] = devs[i].Id
2✔
695
                }
2✔
696
                checkInTimes, err := d.cache.GetCheckInTimes(ctx, tenantID, ids)
2✔
697
                if err != nil {
3✔
698
                        l := log.FromContext(ctx)
1✔
699
                        l.Errorf("Failed to get check-in times for devices")
1✔
700
                } else {
2✔
701
                        for i := range devs {
2✔
702
                                if checkInTimes[i] != nil {
2✔
703
                                        devs[i].CheckInTime = checkInTimes[i]
1✔
704
                                }
1✔
705
                        }
706
                }
707
        }
708

709
        return devs, err
3✔
710
}
711

712
func (d *DevAuth) GetDevice(ctx context.Context, devId string) (*model.Device, error) {
3✔
713
        dev, err := d.db.GetDeviceById(ctx, devId)
3✔
714
        if err != nil {
4✔
715
                if err != store.ErrDevNotFound {
1✔
716
                        return nil, errors.Wrap(err, "db get device by id error")
×
717
                }
×
718
                return nil, err
1✔
719
        }
720

721
        dev.AuthSets, err = d.db.GetAuthSetsForDevice(ctx, dev.Id)
3✔
722
        if err != nil {
3✔
723
                if err != store.ErrAuthSetNotFound {
×
724
                        return nil, errors.Wrap(err, "db get auth sets error")
×
725
                }
×
726
                return dev, nil
×
727
        }
728

729
        if d.cache != nil {
5✔
730
                tenantID := ""
2✔
731
                idData := identity.FromContext(ctx)
2✔
732
                if idData != nil {
4✔
733
                        tenantID = idData.Tenant
2✔
734
                }
2✔
735

736
                checkInTime, err := d.cache.GetCheckInTime(ctx, tenantID, devId)
2✔
737
                if err != nil {
3✔
738
                        l := log.FromContext(ctx)
1✔
739
                        l.Errorf("Failed to get check-in times for device")
1✔
740
                } else if checkInTime != nil {
3✔
741
                        dev.CheckInTime = checkInTime
1✔
742
                }
1✔
743
        }
744

745
        return dev, err
3✔
746
}
747

748
// DecommissionDevice deletes device and all its tokens
749
func (d *DevAuth) DecommissionDevice(ctx context.Context, devID string) error {
9✔
750

9✔
751
        l := log.FromContext(ctx)
9✔
752

9✔
753
        l.Warnf("Decommission device with id: %s", devID)
9✔
754

9✔
755
        err := d.cacheDeleteToken(ctx, devID)
9✔
756
        if err != nil {
12✔
757
                return errors.Wrapf(err, "failed to delete token for %s from cache", devID)
3✔
758
        }
3✔
759

760
        // set decommissioning flag on the device
761
        updev := model.DeviceUpdate{
6✔
762
                Decommissioning: uto.BoolPtr(true),
6✔
763
        }
6✔
764
        if err := d.db.UpdateDevice(
6✔
765
                ctx, devID, updev,
6✔
766
        ); err != nil {
9✔
767
                return err
3✔
768
        }
3✔
769

770
        reqId := requestid.FromContext(ctx)
4✔
771

4✔
772
        tenantID := ""
4✔
773
        idData := identity.FromContext(ctx)
4✔
774
        if idData != nil {
5✔
775
                tenantID = idData.Tenant
1✔
776
        }
1✔
777

778
        // submit device decommissioning job
779
        if err := d.cOrch.SubmitDeviceDecommisioningJob(
4✔
780
                ctx,
4✔
781
                orchestrator.DecommissioningReq{
4✔
782
                        DeviceId:  devID,
4✔
783
                        RequestId: reqId,
4✔
784
                        TenantID:  tenantID,
4✔
785
                }); err != nil {
6✔
786
                return errors.Wrap(err, "submit device decommissioning job error")
2✔
787
        }
2✔
788

789
        return err
3✔
790
}
791

792
// Delete a device and its tokens from deviceauth db
793
func (d *DevAuth) DeleteDevice(ctx context.Context, devID string) error {
7✔
794
        // delete device authorization sets
7✔
795
        if err := d.db.DeleteAuthSetsForDevice(ctx, devID); err != nil &&
7✔
796
                err != store.ErrAuthSetNotFound {
8✔
797
                return errors.Wrap(err, "db delete device authorization sets error")
1✔
798
        }
1✔
799

800
        devOID := oid.FromString(devID)
6✔
801
        // If the devID is not a valid string, there's no token.
6✔
802
        if devOID.String() == "" {
7✔
803
                return ErrInvalidDeviceID
1✔
804
        }
1✔
805
        // delete device tokens
806
        if err := d.db.DeleteTokenByDevId(
5✔
807
                ctx, devOID,
5✔
808
        ); err != nil && err != store.ErrTokenNotFound {
6✔
809
                return errors.Wrap(err, "db delete device tokens error")
1✔
810
        }
1✔
811

812
        // delete device
813
        if err := d.db.DeleteDevice(ctx, devID); err != nil {
6✔
814
                return err
2✔
815
        }
2✔
816

817
        if d.config.EnableReporting {
2✔
818
                if err := d.cOrch.SubmitReindexReporting(ctx, devID); err != nil {
×
819
                        return errors.Wrap(err, "reindex reporting job error")
×
820
                }
×
821
        }
822

823
        return nil
2✔
824
}
825

826
// Deletes device authentication set, and optionally the device.
827
func (d *DevAuth) DeleteAuthSet(ctx context.Context, devID string, authId string) error {
20✔
828

20✔
829
        l := log.FromContext(ctx)
20✔
830

20✔
831
        l.Warnf("Delete authentication set with id: "+
20✔
832
                "%s for the device with id: %s",
20✔
833
                authId, devID)
20✔
834

20✔
835
        err := d.cacheDeleteToken(ctx, devID)
20✔
836
        if err != nil {
22✔
837
                return errors.Wrapf(err, "failed to delete token for %s from cache", devID)
2✔
838
        }
2✔
839

840
        // retrieve device authentication set to check its status
841
        authSet, err := d.db.GetAuthSetById(ctx, authId)
18✔
842
        if err != nil {
21✔
843
                if err == store.ErrAuthSetNotFound {
5✔
844
                        return err
2✔
845
                }
2✔
846
                return errors.Wrap(err, "db get auth set error")
1✔
847
        }
848

849
        // delete device authorization set
850
        if err := d.db.DeleteAuthSetForDevice(ctx, devID, authId); err != nil {
17✔
851
                return err
1✔
852
        }
1✔
853

854
        // if the device authentication set is accepted delete device tokens
855
        if authSet.Status == model.DevStatusAccepted {
16✔
856
                // If string is not a valid UUID there's no token.
1✔
857
                devOID := oid.FromString(devID)
1✔
858
                if err := d.db.DeleteTokenByDevId(
1✔
859
                        ctx, devOID,
1✔
860
                ); err != nil && err != store.ErrTokenNotFound {
2✔
861
                        return errors.Wrap(err,
1✔
862
                                "db delete device tokens error")
1✔
863
                }
1✔
864
        } else if authSet.Status == model.DevStatusPreauth {
17✔
865
                // if the auth set status is 'preauthorized', the device is deleted from
3✔
866
                // deviceauth. We cannot start the decommission_device workflow because
3✔
867
                // we don't provision devices until they are accepted. Still, we need to
3✔
868
                // remove the device from the inventory service because we index pre-authorized
3✔
869
                // devices for consumption via filtering APIs. To trigger the deletion
3✔
870
                // from the inventory service, we start the status update workflow with the
3✔
871
                // special value "decommissioned", which will cause the deletion of the
3✔
872
                // device from the inventory service's database
3✔
873
                tenantID := ""
3✔
874
                idData := identity.FromContext(ctx)
3✔
875
                if idData != nil {
3✔
876
                        tenantID = idData.Tenant
×
877
                }
×
878
                req := orchestrator.UpdateDeviceStatusReq{
3✔
879
                        RequestId: requestid.FromContext(ctx),
3✔
880
                        Devices:   []model.DeviceInventoryUpdate{{Id: devID}},
3✔
881
                        TenantId:  tenantID,
3✔
882
                        Status:    "decommissioned",
3✔
883
                }
3✔
884
                if err = d.cOrch.SubmitUpdateDeviceStatusJob(ctx, req); err != nil {
4✔
885
                        return errors.Wrap(err, "update device status job error")
1✔
886
                }
1✔
887

888
                // delete device
889
                if err := d.db.DeleteDevice(ctx, devID); err != nil {
3✔
890
                        return err
1✔
891
                }
1✔
892

893
                if d.config.EnableReporting {
2✔
894
                        if err := d.cOrch.SubmitReindexReporting(ctx, devID); err != nil {
2✔
895
                                return errors.Wrap(err, "reindex reporting job error")
1✔
896
                        }
1✔
897
                }
898

899
                return nil
×
900
        }
901

902
        return d.updateDeviceStatus(ctx, devID, "", authSet.Status)
11✔
903
}
904

905
func (d *DevAuth) AcceptDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
16✔
906
        l := log.FromContext(ctx)
16✔
907

16✔
908
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
16✔
909
        if err != nil {
18✔
910
                if err == store.ErrAuthSetNotFound {
4✔
911
                        return err
2✔
912
                }
2✔
913
                return errors.Wrap(err, "db get auth set error")
×
914
        }
915

916
        // device authentication set already accepted, nothing to do here
917
        if aset.Status == model.DevStatusAccepted {
16✔
918
                l.Debugf("Device %s already accepted", device_id)
1✔
919
                return nil
1✔
920
        } else if aset.Status != model.DevStatusRejected && aset.Status != model.DevStatusPending {
16✔
921
                // device authentication set can be accepted only from 'pending' or 'rejected' statuses
1✔
922
                return ErrDevAuthBadRequest
1✔
923
        }
1✔
924

925
        // check the device status
926
        // if the device status is accepted then do not trigger provisioning workflow
927
        // this needs to be checked before changing authentication set status
928
        dev, err := d.db.GetDeviceById(ctx, device_id)
13✔
929
        if err != nil {
14✔
930
                return err
1✔
931
        }
1✔
932

933
        // possible race, consider accept-count-unaccept pattern if that's problematic
934
        allow, err := d.canAcceptDevice(ctx)
12✔
935
        if err != nil {
14✔
936
                return err
2✔
937
        }
2✔
938

939
        if !allow {
12✔
940
                return ErrMaxDeviceCountReached
2✔
941
        }
2✔
942

943
        if err := d.setAuthSetStatus(ctx, device_id, auth_id, model.DevStatusAccepted); err != nil {
11✔
944
                return err
3✔
945
        }
3✔
946

947
        if dev.Status != model.DevStatusPending {
7✔
948
                // Device already exist in all services
1✔
949
                // We're done...
1✔
950
                return nil
1✔
951
        }
1✔
952

953
        dev.Status = model.DevStatusAccepted
5✔
954
        aset.Status = model.DevStatusAccepted
5✔
955
        dev.AuthSets = []model.AuthSet{*aset}
5✔
956

5✔
957
        reqId := requestid.FromContext(ctx)
5✔
958

5✔
959
        var tenantID string
5✔
960
        if idty := identity.FromContext(ctx); idty != nil {
5✔
961
                tenantID = idty.Tenant
×
962
        }
×
963

964
        // submit device accepted job
965
        if err := d.cOrch.SubmitProvisionDeviceJob(
5✔
966
                ctx,
5✔
967
                orchestrator.ProvisionDeviceReq{
5✔
968
                        RequestId: reqId,
5✔
969
                        DeviceID:  aset.DeviceId,
5✔
970
                        TenantID:  tenantID,
5✔
971
                        Device:    dev,
5✔
972
                        Status:    dev.Status,
5✔
973
                }); err != nil {
6✔
974
                return errors.Wrap(err, "submit device provisioning job error")
1✔
975
        }
1✔
976

977
        return nil
4✔
978
}
979

980
func (d *DevAuth) setAuthSetStatus(
981
        ctx context.Context,
982
        deviceID string,
983
        authID string,
984
        status string,
985
) error {
15✔
986
        aset, err := d.db.GetAuthSetById(ctx, authID)
15✔
987
        if err != nil {
15✔
988
                if err == store.ErrAuthSetNotFound {
×
989
                        return err
×
990
                }
×
991
                return errors.Wrap(err, "db get auth set error")
×
992
        }
993

994
        if aset.DeviceId != deviceID {
15✔
995
                return ErrDevIdAuthIdMismatch
×
996
        }
×
997

998
        if aset.Status == status {
15✔
999
                return nil
×
1000
        }
×
1001

1002
        currentStatus := aset.Status
15✔
1003

15✔
1004
        if aset.Status == model.DevStatusAccepted &&
15✔
1005
                (status == model.DevStatusRejected || status == model.DevStatusPending) {
22✔
1006
                deviceOID := oid.FromString(aset.DeviceId)
7✔
1007
                // delete device token
7✔
1008
                err := d.db.DeleteTokenByDevId(ctx, deviceOID)
7✔
1009
                if err != nil && err != store.ErrTokenNotFound {
9✔
1010
                        return errors.Wrap(err, "db delete device token error")
2✔
1011
                }
2✔
1012
        }
1013

1014
        // if accepting an auth set
1015
        if status == model.DevStatusAccepted {
21✔
1016
                // reject all accepted auth sets for this device first
8✔
1017
                err := d.db.RejectAuthSetsForDevice(ctx, deviceID)
8✔
1018
                if err != nil && err != store.ErrAuthSetNotFound {
9✔
1019
                        return errors.Wrap(err, "failed to reject auth sets")
1✔
1020
                }
1✔
1021
        }
1022

1023
        if err := d.db.UpdateAuthSetById(ctx, aset.Id, model.AuthSetUpdate{
12✔
1024
                Status: status,
12✔
1025
        }); err != nil {
13✔
1026
                return errors.Wrap(err, "db update device auth set error")
1✔
1027
        }
1✔
1028

1029
        if status == model.DevStatusAccepted {
17✔
1030
                return d.updateDeviceStatus(ctx, deviceID, status, currentStatus)
6✔
1031
        }
6✔
1032
        return d.updateDeviceStatus(ctx, deviceID, "", currentStatus)
6✔
1033
}
1034

1035
func (d *DevAuth) RejectDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
9✔
1036
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
9✔
1037
        if err != nil {
11✔
1038
                if err == store.ErrAuthSetNotFound {
3✔
1039
                        return err
1✔
1040
                }
1✔
1041
                return errors.Wrap(err, "db get auth set error")
1✔
1042
        } else if aset.Status != model.DevStatusPending && aset.Status != model.DevStatusAccepted {
9✔
1043
                // device authentication set can be rejected only from 'accepted' or 'pending' statuses
1✔
1044
                return ErrDevAuthBadRequest
1✔
1045
        }
1✔
1046

1047
        err = d.cacheDeleteToken(ctx, device_id)
7✔
1048
        if err != nil {
9✔
1049
                return errors.Wrapf(err, "failed to delete token for %s from cache", device_id)
2✔
1050
        }
2✔
1051

1052
        return d.setAuthSetStatus(ctx, device_id, auth_id, model.DevStatusRejected)
5✔
1053
}
1054

1055
func (d *DevAuth) ResetDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
5✔
1056
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
5✔
1057
        if err != nil {
6✔
1058
                if err == store.ErrAuthSetNotFound {
1✔
1059
                        return err
×
1060
                }
×
1061
                return errors.Wrap(err, "db get auth set error")
1✔
1062
        } else if aset.Status == model.DevStatusPreauth {
4✔
1063
                // preauthorized auth set should not go into pending state
×
1064
                return ErrDevAuthBadRequest
×
1065
        }
×
1066
        return d.setAuthSetStatus(ctx, device_id, auth_id, model.DevStatusPending)
4✔
1067
}
1068

1069
func parseIdData(idData string) (map[string]interface{}, []byte, error) {
54✔
1070
        var idDataStruct map[string]interface{}
54✔
1071
        var idDataSha256 []byte
54✔
1072

54✔
1073
        err := json.Unmarshal([]byte(idData), &idDataStruct)
54✔
1074
        if err != nil {
56✔
1075
                return idDataStruct, idDataSha256, errors.Wrapf(
2✔
1076
                        err,
2✔
1077
                        "failed to parse identity data: %s",
2✔
1078
                        idData,
2✔
1079
                )
2✔
1080
        }
2✔
1081

1082
        hash := sha256.New()
52✔
1083
        _, _ = hash.Write([]byte(idData))
52✔
1084
        idDataSha256 = hash.Sum(nil)
52✔
1085

52✔
1086
        return idDataStruct, idDataSha256, nil
52✔
1087
}
1088

1089
func (d *DevAuth) PreauthorizeDevice(
1090
        ctx context.Context,
1091
        req *model.PreAuthReq,
1092
) (*model.Device, error) {
7✔
1093
        // try add device, if a device with the given id_data exists -
7✔
1094
        // the unique index on id_data will prevent it (conflict)
7✔
1095
        // this is the only safeguard against id data conflict - we won't try to handle it
7✔
1096
        // additionally on inserting the auth set (can't add an id data index on auth set - would
7✔
1097
        // prevent key rotation)
7✔
1098

7✔
1099
        // FIXME: tenant_token is "" on purpose, will be removed
7✔
1100

7✔
1101
        l := log.FromContext(ctx)
7✔
1102

7✔
1103
        dev := model.NewDevice(req.DeviceId, req.IdData, req.PubKey)
7✔
1104
        dev.Status = model.DevStatusPreauth
7✔
1105

7✔
1106
        idDataStruct, idDataSha256, err := parseIdData(req.IdData)
7✔
1107
        if err != nil {
8✔
1108
                return nil, MakeErrDevAuthBadRequest(err)
1✔
1109
        }
1✔
1110

1111
        dev.IdDataStruct = idDataStruct
6✔
1112
        dev.IdDataSha256 = idDataSha256
6✔
1113

6✔
1114
        err = d.db.AddDevice(ctx, *dev)
6✔
1115
        switch err {
6✔
1116
        case nil:
4✔
1117
                break
4✔
1118
        case store.ErrObjectExists:
1✔
1119
                dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
1✔
1120
                if err != nil {
1✔
1121
                        l.Error("failed to find device but could not preauthorize either")
×
1122
                        return nil, errors.New("failed to preauthorize device")
×
1123
                }
×
1124
                // if device exists we handle force: just add the authset, if force is in req
1125
                if req.Force {
1✔
1126
                        // record authentication request
×
NEW
1127
                        authset := &model.AuthSet{
×
1128
                                Id:           req.AuthSetId,
×
1129
                                IdData:       req.IdData,
×
1130
                                IdDataStruct: idDataStruct,
×
1131
                                IdDataSha256: idDataSha256,
×
1132
                                PubKey:       req.PubKey,
×
1133
                                DeviceId:     dev.Id,
×
1134
                                Status:       model.DevStatusPreauth,
×
1135
                                Timestamp:    uto.TimePtr(time.Now()),
×
1136
                        }
×
NEW
1137
                        err = d.db.UpsertAuthSetStatus(ctx, authset)
×
1138
                        if err != nil {
×
1139
                                return nil, err
×
1140
                        }
×
1141
                        return dev, nil
×
1142
                }
1143
                return dev, ErrDeviceExists
1✔
1144
        default:
1✔
1145
                return nil, errors.Wrap(err, "failed to add device")
1✔
1146
        }
1147

1148
        tenantId := ""
4✔
1149
        idData := identity.FromContext(ctx)
4✔
1150
        if idData != nil {
4✔
1151
                tenantId = idData.Tenant
×
1152
        }
×
1153

1154
        wfReq := orchestrator.UpdateDeviceStatusReq{
4✔
1155
                RequestId: requestid.FromContext(ctx),
4✔
1156
                Devices: []model.DeviceInventoryUpdate{{
4✔
1157
                        Id:       dev.Id,
4✔
1158
                        Revision: dev.Revision,
4✔
1159
                }},
4✔
1160
                TenantId: tenantId,
4✔
1161
                Status:   dev.Status,
4✔
1162
        }
4✔
1163
        if err = d.cOrch.SubmitUpdateDeviceStatusJob(ctx, wfReq); err != nil {
4✔
1164
                return nil, errors.Wrap(err, "update device status job error")
×
1165
        }
×
1166

1167
        // record authentication request
1168
        authset := model.AuthSet{
4✔
1169
                Id:           req.AuthSetId,
4✔
1170
                IdData:       req.IdData,
4✔
1171
                IdDataStruct: idDataStruct,
4✔
1172
                IdDataSha256: idDataSha256,
4✔
1173
                PubKey:       req.PubKey,
4✔
1174
                DeviceId:     req.DeviceId,
4✔
1175
                Status:       model.DevStatusPreauth,
4✔
1176
                Timestamp:    uto.TimePtr(time.Now()),
4✔
1177
        }
4✔
1178

4✔
1179
        err = d.db.AddAuthSet(ctx, authset)
4✔
1180
        switch err {
4✔
1181
        case nil:
2✔
1182
                if err := d.setDeviceIdentity(ctx, dev, tenantId); err != nil {
2✔
1183
                        return nil, err
×
1184
                }
×
1185
                return nil, nil
2✔
1186
        case store.ErrObjectExists:
1✔
1187
                dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
1✔
1188
                if err != nil {
1✔
1189
                        l.Error("failed to find device but could not preauthorize either")
×
1190
                        return nil, errors.New("failed to preauthorize device")
×
1191
                }
×
1192
                return dev, ErrDeviceExists
1✔
1193
        default:
1✔
1194
                return nil, errors.Wrap(err, "failed to add auth set")
1✔
1195
        }
1196
}
1197

1198
func (d *DevAuth) RevokeToken(ctx context.Context, tokenID string) error {
6✔
1199
        l := log.FromContext(ctx)
6✔
1200
        tokenOID := oid.FromString(tokenID)
6✔
1201

6✔
1202
        var token *jwt.Token
6✔
1203
        token, err := d.db.GetToken(ctx, tokenOID)
6✔
1204
        if err != nil {
7✔
1205
                return err
1✔
1206
        }
1✔
1207

1208
        l.Warnf("Revoke token with jti: %s", tokenID)
5✔
1209
        err = d.db.DeleteToken(ctx, tokenOID)
5✔
1210

5✔
1211
        if err == nil && d.cache != nil {
7✔
1212
                err = d.cacheDeleteToken(ctx, token.Claims.Subject.String())
2✔
1213
                err = errors.Wrapf(
2✔
1214
                        err,
2✔
1215
                        "failed to delete token for %s from cache",
2✔
1216
                        token.Claims.Subject.String(),
2✔
1217
                )
2✔
1218
        }
2✔
1219
        return err
5✔
1220
}
1221

1222
func verifyTenantClaim(ctx context.Context, verifyTenant bool, tenant string) error {
18✔
1223

18✔
1224
        l := log.FromContext(ctx)
18✔
1225

18✔
1226
        if verifyTenant {
29✔
1227
                if tenant == "" {
12✔
1228
                        l.Errorf("No tenant claim in the token")
1✔
1229
                        return jwt.ErrTokenInvalid
1✔
1230
                }
1✔
1231
        } else if tenant != "" {
7✔
1232
                l.Errorf("Unexpected tenant claim: %s in the token", tenant)
×
1233
                return jwt.ErrTokenInvalid
×
1234
        }
×
1235

1236
        return nil
17✔
1237
}
1238

1239
func (d *DevAuth) validateJWTToken(ctx context.Context, jti oid.ObjectID, raw string) error {
14✔
1240
        err := d.jwt.Validate(raw)
14✔
1241
        if err != nil && d.jwtFallback != nil {
16✔
1242
                err = d.jwtFallback.Validate(raw)
2✔
1243
        }
2✔
1244
        if err == jwt.ErrTokenExpired && jti.String() != "" {
15✔
1245
                log.FromContext(ctx).Errorf("Token %s expired: %v", jti.String(), err)
1✔
1246
                return d.handleExpiredToken(ctx, jti)
1✔
1247
        } else if err != nil {
16✔
1248
                log.FromContext(ctx).Errorf("Token %s invalid: %v", jti.String(), err)
2✔
1249
                return jwt.ErrTokenInvalid
2✔
1250
        }
2✔
1251
        return nil
12✔
1252
}
1253

1254
func (d *DevAuth) VerifyToken(ctx context.Context, raw string) error {
20✔
1255
        l := log.FromContext(ctx)
20✔
1256

20✔
1257
        token := &jwt.Token{}
20✔
1258
        err := token.UnmarshalJWT([]byte(raw), d.jwt.FromJWT)
20✔
1259
        jti := token.Claims.ID
20✔
1260
        if err != nil {
22✔
1261
                l.Errorf("Token %s invalid: %v", jti.String(), err)
2✔
1262
                return jwt.ErrTokenInvalid
2✔
1263
        } else if !token.Claims.Device {
22✔
1264
                l.Errorf("not a device token")
1✔
1265
                return jwt.ErrTokenInvalid
1✔
1266
        }
1✔
1267

1268
        err = verifyTenantClaim(ctx, d.verifyTenant, token.Claims.Tenant)
18✔
1269
        if err == nil {
35✔
1270
                err = d.checker.ValidateWithContext(ctx)
17✔
1271
        }
17✔
1272
        if err != nil {
19✔
1273
                return err
1✔
1274
        }
1✔
1275

1276
        origMethod := ctxhttpheader.FromContext(ctx, "X-Forwarded-Method")
17✔
1277
        origUri := ctxhttpheader.FromContext(ctx, "X-Forwarded-Uri")
17✔
1278
        origUri = purgeUriArgs(origUri)
17✔
1279

17✔
1280
        // throttle and try fetch token from cache - if cached, it was
17✔
1281
        // already verified against the db checks below, we trust it
17✔
1282
        cachedToken, err := d.cacheThrottleVerify(ctx, token, raw, origMethod, origUri)
17✔
1283

17✔
1284
        if err == cache.ErrTooManyRequests {
18✔
1285
                return err
1✔
1286
        }
1✔
1287

1288
        if cachedToken != "" && raw == cachedToken {
18✔
1289
                // update device check-in time
2✔
1290
                d.updateCheckInTime(
2✔
1291
                        ctx,
2✔
1292
                        token.Claims.Subject.String(),
2✔
1293
                        token.Claims.Tenant,
2✔
1294
                        nil,
2✔
1295
                )
2✔
1296
                return nil
2✔
1297
        }
2✔
1298

1299
        // caching is best effort, don't fail
1300
        if err != nil {
20✔
1301
                l.Errorf("Failed to throttle for token %v: %s, continue.", token, err.Error())
6✔
1302
        }
6✔
1303

1304
        // perform JWT signature and claims validation
1305
        err = d.validateJWTToken(ctx, jti, raw)
14✔
1306
        if err != nil {
17✔
1307
                return err
3✔
1308
        }
3✔
1309

1310
        // cache check was a MISS, hit the db for verification
1311
        // check if token is in the system
1312
        _, err = d.db.GetToken(ctx, jti)
12✔
1313
        if err != nil {
14✔
1314
                if err == store.ErrTokenNotFound {
4✔
1315
                        l.Errorf("Token %s not found", jti.String())
2✔
1316
                        return err
2✔
1317
                }
2✔
1318
                return errors.Wrapf(err, "Cannot get token with id: %s from database: %s", jti, err)
×
1319
        }
1320

1321
        auth, err := d.db.GetAuthSetById(ctx, jti.String())
11✔
1322
        if err != nil {
11✔
1323
                if err == store.ErrAuthSetNotFound {
×
1324
                        l.Errorf("Auth set %s not found", jti.String())
×
1325
                        return err
×
1326
                }
×
1327
                return err
×
1328
        }
1329

1330
        if auth.Status != model.DevStatusAccepted {
12✔
1331
                return jwt.ErrTokenInvalid
1✔
1332
        }
1✔
1333

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

1349
        // update device check-in time
1350
        d.updateCheckInTime(
9✔
1351
                ctx,
9✔
1352
                token.Claims.Subject.String(),
9✔
1353
                token.Claims.Tenant,
9✔
1354
                dev.CheckInTime,
9✔
1355
        )
9✔
1356

9✔
1357
        // after successful token verification - cache it (best effort)
9✔
1358
        _ = d.cacheSetToken(ctx, token, raw)
9✔
1359

9✔
1360
        return nil
9✔
1361
}
1362

1363
func (d *DevAuth) handleExpiredToken(ctx context.Context, jti oid.ObjectID) error {
1✔
1364
        err := d.db.DeleteToken(ctx, jti)
1✔
1365
        if err == store.ErrTokenNotFound {
1✔
1366
                l := log.FromContext(ctx)
×
1367
                l.Errorf("Token %s not found", jti.String())
×
1368
                return err
×
1369
        }
×
1370
        if err != nil {
1✔
1371
                return errors.Wrapf(err, "Cannot delete token with jti: %s : %s", jti, err)
×
1372
        }
×
1373
        return jwt.ErrTokenExpired
1✔
1374
}
1375

1376
// purgeUriArgs removes query string args from an uri string
1377
// important for burst control (bursts are per uri without args)
1378
func purgeUriArgs(uri string) string {
19✔
1379
        return strings.Split(uri, "?")[0]
19✔
1380
}
19✔
1381

1382
func (d *DevAuth) cacheThrottleVerify(
1383
        ctx context.Context,
1384
        token *jwt.Token,
1385
        originalRaw,
1386
        origMethod,
1387
        origUri string,
1388
) (string, error) {
17✔
1389
        if d.cache == nil {
24✔
1390
                return "", nil
7✔
1391
        }
7✔
1392

1393
        // try get cached/precomputed limits
1394
        limits, err := d.getApiLimits(ctx,
10✔
1395
                token.Claims.Tenant,
10✔
1396
                token.Claims.Subject.String())
10✔
1397

10✔
1398
        if err != nil {
14✔
1399
                return "", err
4✔
1400
        }
4✔
1401

1402
        // apply throttling and fetch cached token
1403
        cached, err := d.cache.Throttle(ctx,
6✔
1404
                originalRaw,
6✔
1405
                *limits,
6✔
1406
                token.Claims.Tenant,
6✔
1407
                token.Claims.Subject.String(),
6✔
1408
                cache.IdTypeDevice,
6✔
1409
                origUri,
6✔
1410
                origMethod)
6✔
1411

6✔
1412
        return cached, err
6✔
1413
}
1414

1415
func (d *DevAuth) cacheSetToken(ctx context.Context, token *jwt.Token, raw string) error {
9✔
1416
        if d.cache == nil {
12✔
1417
                return nil
3✔
1418
        }
3✔
1419

1420
        expireIn := time.Duration(token.Claims.ExpiresAt.Unix()-d.clock.Now().Unix()) * time.Second
6✔
1421

6✔
1422
        return d.cache.CacheToken(ctx,
6✔
1423
                token.Claims.Tenant,
6✔
1424
                token.Claims.Subject.String(),
6✔
1425
                cache.IdTypeDevice,
6✔
1426
                raw,
6✔
1427
                expireIn)
6✔
1428
}
1429

1430
func (d *DevAuth) getApiLimits(
1431
        ctx context.Context,
1432
        tid,
1433
        did string,
1434
) (*ratelimits.ApiLimits, error) {
10✔
1435
        limits, err := d.cache.GetLimits(ctx, tid, did, cache.IdTypeDevice)
10✔
1436
        if err != nil {
11✔
1437
                return nil, err
1✔
1438
        }
1✔
1439

1440
        if limits != nil {
14✔
1441
                return limits, nil
5✔
1442
        }
5✔
1443

1444
        dev, err := d.db.GetDeviceById(ctx, did)
4✔
1445
        if err != nil {
4✔
1446
                return nil, err
×
1447
        }
×
1448

1449
        t, err := d.cTenant.GetTenant(ctx, tid)
4✔
1450
        if err != nil {
6✔
1451
                return nil, errors.Wrap(err, "request to get tenant failed")
2✔
1452
        }
2✔
1453
        if t == nil {
2✔
1454
                return nil, errors.New("tenant not found")
×
1455
        }
×
1456

1457
        finalLimits := apiLimitsOverride(t.ApiLimits.DeviceLimits, dev.ApiLimits)
2✔
1458

2✔
1459
        err = d.cache.CacheLimits(ctx, finalLimits, tid, did, cache.IdTypeDevice)
2✔
1460

2✔
1461
        return &finalLimits, err
2✔
1462
}
1463

1464
func (d *DevAuth) cacheDeleteToken(ctx context.Context, did string) error {
36✔
1465
        if d.cache == nil {
60✔
1466
                return nil
24✔
1467
        }
24✔
1468

1469
        idData := identity.FromContext(ctx)
12✔
1470
        if idData == nil {
15✔
1471
                return errors.New("can't unpack tenant identity data from context")
3✔
1472
        }
3✔
1473
        tid := idData.Tenant
9✔
1474

9✔
1475
        return d.cache.DeleteToken(ctx, tid, did, cache.IdTypeDevice)
9✔
1476
}
1477

1478
// TODO move to 'ratelimits', as ApiLimits methods maybe?
1479
func apiLimitsOverride(src, dest ratelimits.ApiLimits) ratelimits.ApiLimits {
9✔
1480
        // override only if not default
9✔
1481
        if dest.ApiQuota.MaxCalls != 0 && dest.ApiQuota.IntervalSec != 0 {
11✔
1482
                src.ApiQuota.MaxCalls = dest.ApiQuota.MaxCalls
2✔
1483
                src.ApiQuota.IntervalSec = dest.ApiQuota.IntervalSec
2✔
1484
        }
2✔
1485

1486
        out := make([]ratelimits.ApiBurst, len(src.ApiBursts))
9✔
1487
        copy(out, src.ApiBursts)
9✔
1488

9✔
1489
        for _, bdest := range dest.ApiBursts {
14✔
1490
                found := false
5✔
1491
                for i, bsrc := range src.ApiBursts {
9✔
1492
                        if bdest.Action == bsrc.Action &&
4✔
1493
                                bdest.Uri == bsrc.Uri {
6✔
1494
                                out[i].MinIntervalSec = bdest.MinIntervalSec
2✔
1495
                                found = true
2✔
1496
                        }
2✔
1497
                }
1498

1499
                if !found {
8✔
1500
                        out = append(out,
3✔
1501
                                ratelimits.ApiBurst{
3✔
1502
                                        Action:         bdest.Action,
3✔
1503
                                        Uri:            bdest.Uri,
3✔
1504
                                        MinIntervalSec: bdest.MinIntervalSec},
3✔
1505
                        )
3✔
1506
                }
3✔
1507
        }
1508

1509
        src.ApiBursts = out
9✔
1510
        return src
9✔
1511
}
1512

1513
func (d *DevAuth) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
23✔
1514
        lim, err := d.db.GetLimit(ctx, name)
23✔
1515

23✔
1516
        switch err {
23✔
1517
        case nil:
17✔
1518
                return lim, nil
17✔
1519
        case store.ErrLimitNotFound:
3✔
1520
                return &model.Limit{Name: name, Value: 0}, nil
3✔
1521
        default:
4✔
1522
                return nil, err
4✔
1523
        }
1524
}
1525

1526
func (d *DevAuth) GetTenantLimit(
1527
        ctx context.Context,
1528
        name,
1529
        tenant_id string,
1530
) (*model.Limit, error) {
4✔
1531
        ctx = identity.WithContext(ctx, &identity.Identity{
4✔
1532
                Tenant: tenant_id,
4✔
1533
        })
4✔
1534

4✔
1535
        return d.GetLimit(ctx, name)
4✔
1536
}
4✔
1537

1538
func (d *DevAuth) WithJWTFallbackHandler(handler jwt.Handler) *DevAuth {
2✔
1539
        d.jwtFallback = handler
2✔
1540
        return d
2✔
1541
}
2✔
1542

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

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

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

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

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

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

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

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

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

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

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

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

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

1609
        if limit.Value == 0 {
20✔
1610
                return true, nil
6✔
1611
        }
6✔
1612

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

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

1622
        return false, nil
3✔
1623
}
1624

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

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

1650
                err = d.db.DeleteTokens(ctx)
3✔
1651
        }
1652

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

1662
        return nil
3✔
1663
}
1664

1665
func (d *DevAuth) cacheFlush(ctx context.Context, tenantID string) error {
4✔
1666
        if d.cache == nil {
4✔
1667
                return nil
×
1668
        }
×
1669

1670
        return d.cache.SuspendTenant(ctx, tenantID)
4✔
1671
}
1672

1673
func (d *DevAuth) GetTenantDeviceStatus(
1674
        ctx context.Context,
1675
        tenantId,
1676
        deviceId string,
1677
) (*model.Status, error) {
3✔
1678
        if tenantId != "" {
6✔
1679
                ctx = identity.WithContext(ctx, &identity.Identity{
3✔
1680
                        Tenant: tenantId,
3✔
1681
                })
3✔
1682
        }
3✔
1683
        dev, err := d.db.GetDeviceById(ctx, deviceId)
3✔
1684
        switch err {
3✔
1685
        case nil:
1✔
1686
                return &model.Status{Status: dev.Status}, nil
1✔
1687
        case store.ErrDevNotFound:
1✔
1688
                return nil, ErrDeviceNotFound
1✔
1689
        default:
1✔
1690
                return nil, errors.Wrapf(err, "get device %s failed", deviceId)
1✔
1691

1692
        }
1693
}
1694

1695
func (d *DevAuth) updateCheckInTime(
1696
        ctx context.Context,
1697
        deviceId string,
1698
        tenantId string,
1699
        previous *time.Time,
1700
) {
16✔
1701
        var err error
16✔
1702
        defer func() {
32✔
1703
                if err != nil {
16✔
1704
                        log.FromContext(ctx).Errorf(
×
1705
                                "failed to update device check-in time for device %s: %s",
×
1706
                                deviceId, err.Error(),
×
1707
                        )
×
1708
                }
×
1709
        }()
1710
        checkInTime := uto.TimePtr(time.Now().UTC())
16✔
1711
        // in case cache is disabled, use mongo
16✔
1712
        if d.cache == nil {
24✔
1713
                if err = d.db.UpdateDevice(ctx,
8✔
1714
                        deviceId,
8✔
1715
                        model.DeviceUpdate{
8✔
1716
                                CheckInTime: checkInTime,
8✔
1717
                        }); err != nil {
8✔
1718
                        return
×
1719
                }
×
1720
        } else {
8✔
1721
                // get check-in time from cache
8✔
1722
                previous, err = d.cache.GetCheckInTime(ctx, tenantId, deviceId)
8✔
1723
                if err != nil {
8✔
1724
                        return
×
1725
                }
×
1726
                // update check-in time in cache
1727
                err = d.cache.CacheCheckInTime(ctx, checkInTime, tenantId, deviceId)
8✔
1728
                if err != nil {
8✔
1729
                        return
×
1730
                }
×
1731
        }
1732
        // compare data without a time of current and previous check-in time
1733
        // and if it's different trigger reindexing (if enabled)
1734
        // and save check-in time in the database
1735
        if previous == nil ||
16✔
1736
                (previous != nil &&
16✔
1737
                        !previous.Truncate(24*time.Hour).Equal(checkInTime.Truncate(24*time.Hour))) {
32✔
1738
                // trigger reindexing
16✔
1739
                if d.config.EnableReporting {
16✔
1740
                        if err = d.cOrch.SubmitReindexReporting(ctx, deviceId); err != nil {
×
1741
                                err = errors.Wrap(err, "reindex reporting job error")
×
1742
                                return
×
1743
                        }
×
1744
                } else {
16✔
1745
                        // update check-in time in inventory
16✔
1746
                        if err := d.syncCheckInTime(ctx, checkInTime, deviceId, tenantId); err != nil {
16✔
1747
                                log.FromContext(ctx).Errorf(
×
1748
                                        "failed to synchronize device check-in time with inventory: device %s: %s",
×
1749
                                        deviceId, err.Error(),
×
1750
                                )
×
1751
                        }
×
1752
                }
1753
                // dump cached value to database
1754
                if d.cache != nil {
24✔
1755
                        if err = d.db.UpdateDevice(ctx,
8✔
1756
                                deviceId,
8✔
1757
                                model.DeviceUpdate{
8✔
1758
                                        CheckInTime: checkInTime,
8✔
1759
                                }); err != nil {
8✔
1760
                                return
×
1761
                        }
×
1762
                }
1763
        }
1764
}
1765

1766
func (d *DevAuth) syncCheckInTime(
1767
        ctx context.Context,
1768
        checkInTime *time.Time,
1769
        deviceId string,
1770
        tenantId string,
1771
) error {
16✔
1772
        attributes := []model.DeviceAttribute{
16✔
1773
                {
16✔
1774
                        Name:        "check_in_time",
16✔
1775
                        Description: nil,
16✔
1776
                        Value:       checkInTime,
16✔
1777
                        Scope:       InventoryScopeSystem,
16✔
1778
                },
16✔
1779
        }
16✔
1780
        attrJson, err := json.Marshal(attributes)
16✔
1781
        if err != nil {
16✔
1782
                return errors.New("internal error: cannot marshal attributes into json")
×
1783
        }
×
1784
        if err := d.cOrch.SubmitUpdateDeviceInventoryJob(
16✔
1785
                ctx,
16✔
1786
                orchestrator.UpdateDeviceInventoryReq{
16✔
1787
                        RequestId:  requestid.FromContext(ctx),
16✔
1788
                        TenantId:   tenantId,
16✔
1789
                        DeviceId:   deviceId,
16✔
1790
                        Scope:      InventoryScopeSystem,
16✔
1791
                        Attributes: string(attrJson),
16✔
1792
                }); err != nil {
16✔
1793
                return errors.Wrap(err, "failed to start device inventory update job")
×
1794
        }
×
1795
        return nil
16✔
1796
}
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