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

mendersoftware / mender-server / 1550767116

19 Nov 2024 11:58AM UTC coverage: 72.563% (-0.2%) from 72.771%
1550767116

Pull #202

gitlab-ci

alfrunes
test: Fix deviceauth.cache tests after changing client initialization

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #202: MEN-7733: Rate limits for devices APIs

4174 of 6072 branches covered (68.74%)

Branch coverage included in aggregate %.

135 of 380 new or added lines in 8 files covered. (35.53%)

84 existing lines in 3 files now uncovered.

42593 of 58378 relevant lines covered (72.96%)

15.45 hits per line

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

85.85
/backend/services/deviceauth/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/pkg/errors"
24

25
        "github.com/mendersoftware/mender-server/pkg/addons"
26
        ctxhttpheader "github.com/mendersoftware/mender-server/pkg/context/httpheader"
27
        "github.com/mendersoftware/mender-server/pkg/identity"
28
        "github.com/mendersoftware/mender-server/pkg/log"
29
        "github.com/mendersoftware/mender-server/pkg/mongo/oid"
30
        "github.com/mendersoftware/mender-server/pkg/plan"
31
        "github.com/mendersoftware/mender-server/pkg/rate"
32
        "github.com/mendersoftware/mender-server/pkg/ratelimits"
33
        "github.com/mendersoftware/mender-server/pkg/requestid"
34

35
        "github.com/mendersoftware/mender-server/services/deviceauth/access"
36
        "github.com/mendersoftware/mender-server/services/deviceauth/cache"
37
        "github.com/mendersoftware/mender-server/services/deviceauth/client/inventory"
38
        "github.com/mendersoftware/mender-server/services/deviceauth/client/orchestrator"
39
        "github.com/mendersoftware/mender-server/services/deviceauth/client/tenant"
40
        "github.com/mendersoftware/mender-server/services/deviceauth/jwt"
41
        "github.com/mendersoftware/mender-server/services/deviceauth/model"
42
        "github.com/mendersoftware/mender-server/services/deviceauth/store"
43
        "github.com/mendersoftware/mender-server/services/deviceauth/utils"
44
        uto "github.com/mendersoftware/mender-server/services/deviceauth/utils/to"
45
)
46

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

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

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

65
func IsErrDevAuthUnauthorized(e error) bool {
×
66
        return strings.HasPrefix(e.Error(), MsgErrDevAuthUnauthorized)
×
67
}
×
68

69
func MakeErrDevAuthUnauthorized(e error) error {
1✔
70
        return errors.Wrap(e, MsgErrDevAuthUnauthorized)
1✔
71
}
1✔
72

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

77
func MakeErrDevAuthBadRequest(e error) error {
1✔
78
        return errors.Wrap(e, MsgErrDevAuthBadRequest)
1✔
79
}
1✔
80

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

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

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

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

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

113
        GetDevCountByStatus(ctx context.Context, status string) (int, error)
114

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

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

131
        // deviceRatelimiter is used to limit authenticated device requests
132
        rateLimiter        rate.Limiter
133
        rateLimiterWeights map[string]float64
134
}
135

136
type Config struct {
137
        // token issuer
138
        Issuer string
139
        // token expiration time
140
        ExpirationTime int64
141
        // Default tenant token to use when the client supplies none. Can be
142
        // empty
143
        DefaultTenantToken string
144
        InventoryAddr      string
145

146
        EnableReporting bool
147
        HaveAddons      bool
148
}
149

150
func NewDevAuth(d store.DataStore, co orchestrator.ClientRunner,
151
        jwt jwt.Handler, config Config,
152
) *DevAuth {
1✔
153
        // initialize checker using an empty merge (returns nil on validate)
1✔
154
        checker := access.Merge()
1✔
155
        if config.HaveAddons {
1✔
UNCOV
156
                checker = access.NewAddonChecker()
×
157
        }
×
158

159
        return &DevAuth{
1✔
160
                db:           d,
1✔
161
                invClient:    inventory.NewClient(config.InventoryAddr, false),
1✔
162
                cOrch:        co,
1✔
163
                jwt:          jwt,
1✔
164
                verifyTenant: false,
1✔
165
                config:       config,
1✔
166
                clock:        utils.NewClock(),
1✔
167
                checker:      checker,
1✔
168
        }
1✔
169
}
170

171
func (d *DevAuth) HealthCheck(ctx context.Context) error {
1✔
172
        err := d.db.Ping(ctx)
1✔
173
        if err != nil {
2✔
174
                return errors.Wrap(err, "error reaching MongoDB")
1✔
175
        }
1✔
176
        err = d.invClient.CheckHealth(ctx)
1✔
177
        if err != nil {
2✔
178
                return errors.Wrap(err, "Inventory service unhealthy")
1✔
179
        }
1✔
180
        err = d.cOrch.CheckHealth(ctx)
1✔
181
        if err != nil {
2✔
182
                return errors.Wrap(err, "Workflows service unhealthy")
1✔
183
        }
1✔
184
        if d.verifyTenant {
2✔
185
                err = d.cTenant.CheckHealth(ctx)
1✔
186
                if err != nil {
2✔
187
                        return errors.Wrap(err, "Tenantadm service unhealthy")
1✔
188
                }
1✔
189
        }
190
        return nil
1✔
191
}
192

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

233
func (d *DevAuth) getDeviceFromAuthRequest(
234
        ctx context.Context,
235
        r *model.AuthReq,
236
) (*model.Device, error) {
1✔
237
        dev := model.NewDevice("", r.IdData, r.PubKey)
1✔
238

1✔
239
        l := log.FromContext(ctx)
1✔
240

1✔
241
        idDataStruct, idDataSha256, err := parseIdData(r.IdData)
1✔
242
        if err != nil {
1✔
UNCOV
243
                return nil, MakeErrDevAuthBadRequest(err)
×
244
        }
×
245

246
        dev.IdDataStruct = idDataStruct
1✔
247
        dev.IdDataSha256 = idDataSha256
1✔
248

1✔
249
        // record device
1✔
250
        err = d.db.AddDevice(ctx, *dev)
1✔
251
        addDeviceErr := err
1✔
252
        if err != nil && err != store.ErrObjectExists {
2✔
253
                l.Errorf("failed to add/find device: %v", err)
1✔
254
                return nil, err
1✔
255
        }
1✔
256

257
        // either the device was added or it was already present, in any case,
258
        // pull it from DB
259
        dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
1✔
260
        if err != nil {
2✔
261
                l.Error("failed to find device but could not add either")
1✔
262
                return nil, errors.New("failed to locate device")
1✔
263
        }
1✔
264

265
        idData := identity.FromContext(ctx)
1✔
266
        tenantId := ""
1✔
267
        if idData != nil {
2✔
268
                tenantId = idData.Tenant
1✔
269
        }
1✔
270
        if addDeviceErr != store.ErrObjectExists {
2✔
271
                if err := d.setDeviceIdentity(ctx, dev, tenantId); err != nil {
1✔
UNCOV
272
                        return nil, err
×
273
                }
×
274
        }
275

276
        // check if the device is in the decommissioning state
277
        if dev.Decommissioning {
1✔
UNCOV
278
                l.Warnf("Device %s in the decommissioning state.", dev.Id)
×
279
                return nil, ErrDevAuthUnauthorized
×
280
        }
×
281

282
        return dev, nil
1✔
283
}
284

285
func (d *DevAuth) signToken(ctx context.Context) jwt.SignFunc {
1✔
286
        return func(t *jwt.Token) (string, error) {
2✔
287
                return d.jwt.ToJWT(t)
1✔
288
        }
1✔
289
}
290

291
func (d *DevAuth) doVerifyTenant(ctx context.Context, token string) (*tenant.Tenant, error) {
1✔
292
        t, err := d.cTenant.VerifyToken(ctx, token)
1✔
293
        if err != nil {
2✔
294
                if tenant.IsErrTokenVerificationFailed(err) {
2✔
295
                        return nil, MakeErrDevAuthUnauthorized(err)
1✔
296
                }
1✔
297

298
                return nil, errors.Wrap(err, "request to verify tenant token failed")
1✔
299
        }
300

301
        return t, nil
1✔
302
}
303

304
func (d *DevAuth) getTenantWithDefault(
305
        ctx context.Context,
306
        tenantToken,
307
        defaultToken string,
308
) (context.Context, *tenant.Tenant, error) {
1✔
309
        l := log.FromContext(ctx)
1✔
310

1✔
311
        if tenantToken == "" && defaultToken == "" {
2✔
312
                return nil, nil, MakeErrDevAuthUnauthorized(errors.New("tenant token missing"))
1✔
313
        }
1✔
314

315
        var t *tenant.Tenant
1✔
316
        var err error
1✔
317

1✔
318
        // try the provided token
1✔
319
        // but continue on errors and maybe try the default token
1✔
320
        if tenantToken != "" {
2✔
321
                t, err = d.doVerifyTenant(ctx, tenantToken)
1✔
322
                if err != nil {
2✔
323
                        l.Errorf("Failed to verify supplied tenant token: %s", err.Error())
1✔
324
                }
1✔
325
        }
326

327
        // if we still haven't selected a tenant - the token didn't work
328
        // try the default one
329
        if t == nil && defaultToken != "" {
2✔
330
                t, err = d.doVerifyTenant(ctx, defaultToken)
1✔
331
                if err != nil {
2✔
332
                        l.Errorf("Failed to verify default tenant token: %s", err.Error())
1✔
333
                }
1✔
334
        }
335

336
        // none of the tokens worked
337
        if err != nil {
2✔
338
                if tenant.IsErrTokenVerificationFailed(err) {
1✔
UNCOV
339
                        return ctx, nil, MakeErrDevAuthUnauthorized(err)
×
340
                }
×
341
                return ctx, nil, err
1✔
342
        }
343

344
        tCtx := identity.WithContext(ctx, &identity.Identity{
1✔
345
                Subject: "internal",
1✔
346
                Tenant:  t.ID,
1✔
347
        })
1✔
348

1✔
349
        return tCtx, t, nil
1✔
350
}
351

352
func (d *DevAuth) SubmitAuthRequest(ctx context.Context, r *model.AuthReq) (string, error) {
1✔
353
        l := log.FromContext(ctx)
1✔
354

1✔
355
        var tenant *tenant.Tenant
1✔
356
        var err error
1✔
357

1✔
358
        if d.verifyTenant {
2✔
359
                ctx, tenant, err = d.getTenantWithDefault(ctx, r.TenantToken, d.config.DefaultTenantToken)
1✔
360
                if err != nil {
2✔
361
                        return "", err
1✔
362
                }
1✔
363
        } else {
1✔
364
                // ignore identity data when tenant verification is off
1✔
365
                // it's possible that the device will provide old auth token or old tenant token
1✔
366
                // in the authorization header;
1✔
367
                // in that case we need to wipe identity data from the context
1✔
368
                ctx = identity.WithContext(ctx, nil)
1✔
369
        }
1✔
370

371
        // first, try to handle preauthorization
372
        authSet, err := d.processPreAuthRequest(ctx, r)
1✔
373
        if err != nil {
2✔
374
                return "", err
1✔
375
        }
1✔
376

377
        // if not a preauth request, process with regular auth request handling
378
        if authSet == nil {
2✔
379
                authSet, err = d.processAuthRequest(ctx, r)
1✔
380
                if err != nil {
2✔
381
                        return "", err
1✔
382
                }
1✔
383
        }
384

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

1✔
408
                if d.verifyTenant {
2✔
409
                        token.Claims.Tenant = tenant.ID
1✔
410
                        token.Claims.Plan = tenant.Plan
1✔
411
                        token.Claims.Addons = tenant.Addons
1✔
412
                        token.Claims.Trial = tenant.Trial
1✔
413
                } else {
2✔
414
                        token.Claims.Plan = plan.PlanEnterprise
1✔
415
                        token.Addons = addons.AllAddonsEnabled
1✔
416
                }
1✔
417

418
                // sign and encode as JWT
419
                raw, err := token.MarshalJWT(d.signToken(ctx))
1✔
420
                if err != nil {
1✔
UNCOV
421
                        return "", errors.Wrap(err, "generate token error")
×
422
                }
×
423

424
                if err := d.db.AddToken(ctx, token); err != nil {
1✔
UNCOV
425
                        return "", errors.Wrap(err, "add token error")
×
426
                }
×
427

428
                l.Infof("Token %s assigned to device %s",
1✔
429
                        token.Claims.ID, token.Claims.Subject)
1✔
430
                d.updateCheckInTime(ctx, authSet.DeviceId, token.Claims.Tenant, nil)
1✔
431
                return string(raw), nil
1✔
432
        }
433

434
        // no token, return device unauthorized
435
        return "", ErrDevAuthUnauthorized
1✔
436
}
437

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

451
        // check if the device is in the decommissioning state
452
        if dev.Decommissioning {
2✔
453
                l := log.FromContext(ctx)
1✔
454
                l.Warnf("Device %s in the decommissioning state.", dev.Id)
1✔
455
                return nil, ErrDevAuthUnauthorized
1✔
456
        }
1✔
457

458
        currentStatus := dev.Status
1✔
459
        if dev.Status != model.DevStatusAccepted {
2✔
460
                // auth set is ok for auto-accepting, check device limit
1✔
461
                allow, err := d.canAcceptDevice(ctx)
1✔
462
                if err != nil {
2✔
463
                        return nil, err
1✔
464
                }
1✔
465

466
                if !allow {
2✔
467
                        return nil, ErrMaxDeviceCountReached
1✔
468
                }
1✔
469
        }
470

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

484
        if err := d.updateDeviceStatus(
1✔
485
                ctx,
1✔
486
                aset.DeviceId,
1✔
487
                model.DevStatusAccepted,
1✔
488
                currentStatus,
1✔
489
        ); err != nil {
1✔
UNCOV
490
                return nil, err
×
491
        }
×
492

493
        aset.Status = model.DevStatusAccepted
1✔
494
        dev.Status = model.DevStatusAccepted
1✔
495
        dev.AuthSets = append(dev.AuthSets, *aset)
1✔
496

1✔
497
        if !deviceAlreadyAccepted {
2✔
498
                reqId := requestid.FromContext(ctx)
1✔
499
                var tenantID string
1✔
500
                if idty := identity.FromContext(ctx); idty != nil {
1✔
UNCOV
501
                        tenantID = idty.Tenant
×
502
                }
×
503

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

520
func (d *DevAuth) processPreAuthRequest(
521
        ctx context.Context,
522
        r *model.AuthReq,
523
) (*model.AuthSet, error) {
1✔
524
        _, idDataSha256, err := parseIdData(r.IdData)
1✔
525
        if err != nil {
2✔
526
                return nil, MakeErrDevAuthBadRequest(err)
1✔
527
        }
1✔
528

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

545
        // if authset status is not 'preauthorized', nothing to do
546
        if aset.Status != model.DevStatusPreauth {
2✔
547
                return nil, nil
1✔
548
        }
1✔
549
        return d.handlePreAuthDevice(ctx, aset)
1✔
550
}
551

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

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

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

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

606
        if d.config.EnableReporting {
2✔
607
                if err := d.cOrch.SubmitReindexReporting(ctx, devId); err != nil {
2✔
608
                        return errors.Wrap(err, "reindex reporting job error")
1✔
609
                }
1✔
610
        }
611

612
        return nil
1✔
613
}
614

615
// processAuthRequest will process incoming auth request and record authentication
616
// data information it contains. Returns a tupe (auth set, error). If no errors were
617
// present, model.AuthSet.Status will indicate the status of device admission
618
func (d *DevAuth) processAuthRequest(
619
        ctx context.Context,
620
        r *model.AuthReq,
621
) (*model.AuthSet, error) {
1✔
622
        l := log.FromContext(ctx)
1✔
623

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

630
        idDataStruct, idDataSha256, err := parseIdData(r.IdData)
1✔
631
        if err != nil {
1✔
UNCOV
632
                return nil, MakeErrDevAuthBadRequest(err)
×
633
        }
×
634

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

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

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

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

665
        return areq, nil
1✔
666
}
667

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

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

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

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

711
        return devs, err
1✔
712
}
713

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

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

731
        if d.cache != nil {
2✔
732
                tenantID := ""
1✔
733
                idData := identity.FromContext(ctx)
1✔
734
                if idData != nil {
2✔
735
                        tenantID = idData.Tenant
1✔
736
                }
1✔
737

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

747
        return dev, err
1✔
748
}
749

750
// DecommissionDevice deletes device and all its tokens
751
func (d *DevAuth) DecommissionDevice(ctx context.Context, devID string) error {
1✔
752
        l := log.FromContext(ctx)
1✔
753

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

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

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

771
        reqId := requestid.FromContext(ctx)
1✔
772

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

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

790
        return err
1✔
791
}
792

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

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

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

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

824
        return nil
1✔
825
}
826

827
// Deletes device authentication set, and optionally the device.
828
func (d *DevAuth) DeleteAuthSet(ctx context.Context, devID string, authId string) error {
1✔
829
        l := log.FromContext(ctx)
1✔
830

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

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

840
        // retrieve device authentication set to check its status
841
        authSet, err := d.db.GetAuthSetById(ctx, authId)
1✔
842
        if err != nil {
2✔
843
                if err == store.ErrAuthSetNotFound {
2✔
844
                        return err
1✔
845
                }
1✔
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 {
2✔
851
                return err
1✔
852
        }
1✔
853

854
        // if the device authentication set is accepted delete device tokens
855
        if authSet.Status == model.DevStatusAccepted {
2✔
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 {
2✔
865
                // if the auth set status is 'preauthorized', the device is deleted from
1✔
866
                // deviceauth. We cannot start the decommission_device workflow because
1✔
867
                // we don't provision devices until they are accepted. Still, we need to
1✔
868
                // remove the device from the inventory service because we index pre-authorized
1✔
869
                // devices for consumption via filtering APIs. To trigger the deletion
1✔
870
                // from the inventory service, we start the status update workflow with the
1✔
871
                // special value "decommissioned", which will cause the deletion of the
1✔
872
                // device from the inventory service's database
1✔
873
                tenantID := ""
1✔
874
                idData := identity.FromContext(ctx)
1✔
875
                if idData != nil {
1✔
UNCOV
876
                        tenantID = idData.Tenant
×
877
                }
×
878
                req := orchestrator.UpdateDeviceStatusReq{
1✔
879
                        RequestId: requestid.FromContext(ctx),
1✔
880
                        Devices:   []model.DeviceInventoryUpdate{{Id: devID}},
1✔
881
                        TenantId:  tenantID,
1✔
882
                        Status:    "decommissioned",
1✔
883
                }
1✔
884
                if err = d.cOrch.SubmitUpdateDeviceStatusJob(ctx, req); err != nil {
2✔
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 {
2✔
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

UNCOV
899
                return nil
×
900
        }
901

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

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

1✔
908
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
1✔
909
        if err != nil {
2✔
910
                if err == store.ErrAuthSetNotFound {
2✔
911
                        return err
1✔
912
                }
1✔
UNCOV
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 {
2✔
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 {
3✔
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)
1✔
929
        if err != nil {
2✔
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)
1✔
935
        if err != nil {
2✔
936
                return err
1✔
937
        }
1✔
938

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

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

947
        if dev.Status != model.DevStatusPending {
2✔
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
1✔
954
        aset.Status = model.DevStatusAccepted
1✔
955
        dev.AuthSets = []model.AuthSet{*aset}
1✔
956

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

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

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

977
        return nil
1✔
978
}
979

980
func (d *DevAuth) setAuthSetStatus(
981
        ctx context.Context,
982
        deviceID string,
983
        authID string,
984
        status string,
985
) error {
1✔
986
        aset, err := d.db.GetAuthSetById(ctx, authID)
1✔
987
        if err != nil {
1✔
UNCOV
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 {
1✔
UNCOV
995
                return ErrDevIdAuthIdMismatch
×
996
        }
×
997

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

1002
        currentStatus := aset.Status
1✔
1003

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

1014
        // if accepting an auth set
1015
        if status == model.DevStatusAccepted {
2✔
1016
                // reject all accepted auth sets for this device first
1✔
1017
                err := d.db.RejectAuthSetsForDevice(ctx, deviceID)
1✔
1018
                if err != nil && err != store.ErrAuthSetNotFound {
2✔
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{
1✔
1024
                Status: status,
1✔
1025
        }); err != nil {
2✔
1026
                return errors.Wrap(err, "db update device auth set error")
1✔
1027
        }
1✔
1028

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

1035
func (d *DevAuth) RejectDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
1✔
1036
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
1✔
1037
        if err != nil {
2✔
1038
                if err == store.ErrAuthSetNotFound {
1✔
UNCOV
1039
                        return err
×
1040
                }
×
1041
                return errors.Wrap(err, "db get auth set error")
1✔
1042
        } else if aset.Status != model.DevStatusPending && aset.Status != model.DevStatusAccepted {
2✔
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)
1✔
1048
        if err != nil {
2✔
1049
                return errors.Wrapf(err, "failed to delete token for %s from cache", device_id)
1✔
1050
        }
1✔
1051

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

1055
func (d *DevAuth) ResetDeviceAuth(ctx context.Context, device_id string, auth_id string) error {
1✔
1056
        aset, err := d.db.GetAuthSetById(ctx, auth_id)
1✔
1057
        if err != nil {
2✔
1058
                if err == store.ErrAuthSetNotFound {
1✔
UNCOV
1059
                        return err
×
1060
                }
×
1061
                return errors.Wrap(err, "db get auth set error")
1✔
1062
        } else if aset.Status == model.DevStatusPreauth {
1✔
UNCOV
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)
1✔
1067
}
1068

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

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

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

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

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

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

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

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

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

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

1✔
1114
        err = d.db.AddDevice(ctx, *dev)
1✔
1115
        switch err {
1✔
1116
        case nil:
1✔
1117
                break
1✔
1118
        case store.ErrObjectExists:
1✔
1119
                dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
1✔
1120
                if err != nil {
1✔
UNCOV
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✔
UNCOV
1126
                        // record authentication request
×
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
                        }
×
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 := ""
1✔
1149
        idData := identity.FromContext(ctx)
1✔
1150
        if idData != nil {
1✔
UNCOV
1151
                tenantId = idData.Tenant
×
1152
        }
×
1153

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

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

1✔
1179
        err = d.db.AddAuthSet(ctx, authset)
1✔
1180
        switch err {
1✔
1181
        case nil:
1✔
1182
                if err := d.setDeviceIdentity(ctx, dev, tenantId); err != nil {
1✔
UNCOV
1183
                        return nil, err
×
1184
                }
×
1185
                return nil, nil
1✔
1186
        case store.ErrObjectExists:
1✔
1187
                dev, err = d.db.GetDeviceByIdentityDataHash(ctx, idDataSha256)
1✔
1188
                if err != nil {
1✔
UNCOV
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 {
1✔
1199
        l := log.FromContext(ctx)
1✔
1200
        tokenOID := oid.FromString(tokenID)
1✔
1201

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

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

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

1222
func verifyTenantClaim(ctx context.Context, verifyTenant bool, tenant string) error {
1✔
1223
        l := log.FromContext(ctx)
1✔
1224

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

1235
        return nil
1✔
1236
}
1237

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

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

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

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

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

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

1✔
1283
        if err == cache.ErrTooManyRequests {
2✔
1284
                return err
1✔
1285
        } else if errRate := d.checkRateLimits(ctx); errRate != nil {
2✔
NEW
1286
                return errRate
×
1287
        }
×
1288

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

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

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

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

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

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

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

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

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

1✔
1361
        return nil
1✔
1362
}
1363

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

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

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

1394
        // try get cached/precomputed limits
1395
        limits, err := d.getApiLimits(ctx,
1✔
1396
                token.Claims.Tenant,
1✔
1397
                token.Claims.Subject.String())
1✔
1398
        if err != nil {
2✔
1399
                return "", err
1✔
1400
        }
1✔
1401

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

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

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

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

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

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

1440
        if limits != nil {
2✔
1441
                return limits, nil
1✔
1442
        }
1✔
1443

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

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

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

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

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

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

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

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

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

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

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

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

1510
        src.ApiBursts = out
1✔
1511
        return src
1✔
1512
}
1513

1514
func (d *DevAuth) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
1✔
1515
        l := log.FromContext(ctx)
1✔
1516
        var (
1✔
1517
                limit *model.Limit
1✔
1518
                err   error
1✔
1519
        )
1✔
1520
        if d.cache != nil {
1✔
NEW
1521
                limit, err = d.cache.GetLimit(ctx, name)
×
NEW
1522
                if err != nil {
×
NEW
1523
                        l.Warnf("error fetching limit from cache: %s", err.Error())
×
NEW
1524
                }
×
1525
        }
1526
        if limit == nil {
2✔
1527
                limit, err = d.db.GetLimit(ctx, name)
1✔
1528
                if err != nil {
2✔
1529
                        if errors.Is(err, store.ErrLimitNotFound) {
2✔
1530
                                limit = &model.Limit{Name: name, Value: 0}
1✔
1531
                                err = nil
1✔
1532
                        } else {
2✔
1533
                                return nil, err
1✔
1534
                        }
1✔
1535
                }
1536
                if d.cache != nil {
1✔
NEW
1537
                        errCache := d.cache.SetLimit(ctx, limit)
×
NEW
1538
                        if errCache != nil {
×
NEW
1539
                                l.Warnf("failed to store limit %q in cache: %s", name, errCache.Error())
×
NEW
1540
                        }
×
1541
                }
1542
        }
1543
        return limit, err
1✔
1544
}
1545

1546
func (d *DevAuth) GetTenantLimit(
1547
        ctx context.Context,
1548
        name,
1549
        tenant_id string,
1550
) (*model.Limit, error) {
1✔
1551
        ctx = identity.WithContext(ctx, &identity.Identity{
1✔
1552
                Tenant: tenant_id,
1✔
1553
        })
1✔
1554

1✔
1555
        return d.GetLimit(ctx, name)
1✔
1556
}
1✔
1557

1558
func (d *DevAuth) WithJWTFallbackHandler(handler jwt.Handler) *DevAuth {
1✔
1559
        d.jwtFallback = handler
1✔
1560
        return d
1✔
1561
}
1✔
1562

1563
// WithTenantVerification will force verification of tenant token with tenant
1564
// administrator when processing device authentication requests. Returns an
1565
// updated devauth.
1566
func (d *DevAuth) WithTenantVerification(c tenant.ClientRunner) *DevAuth {
1✔
1567
        d.cTenant = c
1✔
1568
        d.verifyTenant = true
1✔
1569
        return d
1✔
1570
}
1✔
1571

1572
func (d *DevAuth) WithCache(c cache.Cache) *DevAuth {
1✔
1573
        d.cache = c
1✔
1574
        return d
1✔
1575
}
1✔
1576

1577
func (d *DevAuth) WithRatelimits(
1578
        rl rate.Limiter,
1579
        weights map[string]float64,
1580
) *DevAuth {
1✔
1581
        if rl != nil {
2✔
1582
                d.rateLimiter = rl
1✔
1583
                d.rateLimiterWeights = weights
1✔
1584
        }
1✔
1585
        return d
1✔
1586
}
1587

1588
func (d *DevAuth) WithClock(c utils.Clock) *DevAuth {
1✔
1589
        d.clock = c
1✔
1590
        return d
1✔
1591
}
1✔
1592

1593
func (d *DevAuth) SetTenantLimit(ctx context.Context, tenant_id string, limit model.Limit) error {
1✔
1594
        l := log.FromContext(ctx)
1✔
1595

1✔
1596
        ctx = identity.WithContext(ctx, &identity.Identity{
1✔
1597
                Tenant: tenant_id,
1✔
1598
        })
1✔
1599

1✔
1600
        l.Infof("setting limit %v for tenant %v", limit, tenant_id)
1✔
1601

1✔
1602
        if err := d.db.PutLimit(ctx, limit); err != nil {
2✔
1603
                l.Errorf("failed to save limit %v for tenant %v to database: %v",
1✔
1604
                        limit, tenant_id, err)
1✔
1605
                return errors.Wrapf(err, "failed to save limit %v for tenant %v to database",
1✔
1606
                        limit, tenant_id)
1✔
1607
        }
1✔
1608
        if d.cache != nil {
1✔
NEW
UNCOV
1609
                errCache := d.cache.SetLimit(ctx, &limit)
×
NEW
UNCOV
1610
                if errCache != nil {
×
NEW
1611
                        l.Warnf("failed to store limit %q in cache: %s", limit.Name, errCache.Error())
×
NEW
1612
                }
×
1613
        }
1614
        return nil
1✔
1615
}
1616

1617
func (d *DevAuth) DeleteTenantLimit(ctx context.Context, tenant_id string, limit string) error {
1✔
1618
        l := log.FromContext(ctx)
1✔
1619

1✔
1620
        ctx = identity.WithContext(ctx, &identity.Identity{
1✔
1621
                Tenant: tenant_id,
1✔
1622
        })
1✔
1623

1✔
1624
        l.Infof("removing limit %v for tenant %v", limit, tenant_id)
1✔
1625

1✔
1626
        if err := d.db.DeleteLimit(ctx, limit); err != nil {
2✔
1627
                l.Errorf("failed to delete limit %v for tenant %v to database: %v",
1✔
1628
                        limit, tenant_id, err)
1✔
1629
                return errors.Wrapf(err, "failed to delete limit %v for tenant %v to database",
1✔
1630
                        limit, tenant_id)
1✔
1631
        }
1✔
1632
        if d.cache != nil {
1✔
NEW
UNCOV
1633
                errCache := d.cache.DeleteLimit(ctx, limit)
×
NEW
UNCOV
1634
                if errCache != nil {
×
NEW
1635
                        l.Warnf("error removing limit %q from cache: %s", limit, errCache.Error())
×
NEW
1636
                }
×
1637
        }
1638
        return nil
1✔
1639
}
1640

1641
func (d *DevAuth) GetDevCountByStatus(ctx context.Context, status string) (int, error) {
1✔
1642
        return d.db.GetDevCountByStatus(ctx, status)
1✔
1643
}
1✔
1644

1645
// canAcceptDevice checks if model.LimitMaxDeviceCount will be exceeded
1646
func (d *DevAuth) canAcceptDevice(ctx context.Context) (bool, error) {
1✔
1647
        limit, err := d.GetLimit(ctx, model.LimitMaxDeviceCount)
1✔
1648
        if err != nil {
2✔
1649
                return false, errors.Wrap(err, "can't get current device limit")
1✔
1650
        }
1✔
1651

1652
        if limit.Value == 0 {
2✔
1653
                return true, nil
1✔
1654
        }
1✔
1655

1656
        accepted, err := d.db.GetDevCountByStatus(ctx, model.DevStatusAccepted)
1✔
1657
        if err != nil {
2✔
1658
                return false, errors.Wrap(err, "can't get current device count")
1✔
1659
        }
1✔
1660

1661
        if uint64(accepted+1) <= limit.Value {
2✔
1662
                return true, nil
1✔
1663
        }
1✔
1664

1665
        return false, nil
1✔
1666
}
1667

1668
func (d *DevAuth) DeleteTokens(
1669
        ctx context.Context,
1670
        tenantID string,
1671
        deviceID string,
1672
) error {
1✔
1673
        var err error
1✔
1674
        ctx = identity.WithContext(ctx, &identity.Identity{
1✔
1675
                Tenant: tenantID,
1✔
1676
        })
1✔
1677

1✔
1678
        if deviceID != "" {
2✔
1679
                deviceOID := oid.FromString(deviceID)
1✔
1680
                if deviceOID.String() == "" {
1✔
UNCOV
1681
                        return ErrInvalidAuthSetID
×
UNCOV
1682
                }
×
1683
                err = d.db.DeleteTokenByDevId(ctx, deviceOID)
1✔
1684
        } else {
1✔
1685
                if err := d.cacheFlush(ctx, tenantID); err != nil {
2✔
1686
                        return errors.Wrapf(
1✔
1687
                                err,
1✔
1688
                                "failed to flush cache when cleaning tokens for tenant %v",
1✔
1689
                                tenantID,
1✔
1690
                        )
1✔
1691
                }
1✔
1692

1693
                err = d.db.DeleteTokens(ctx)
1✔
1694
        }
1695

1696
        if err != nil && err != store.ErrTokenNotFound {
2✔
1697
                return errors.Wrapf(
1✔
1698
                        err,
1✔
1699
                        "failed to delete tokens for tenant: %v, device id: %v",
1✔
1700
                        tenantID,
1✔
1701
                        deviceID,
1✔
1702
                )
1✔
1703
        }
1✔
1704

1705
        return nil
1✔
1706
}
1707

1708
func (d *DevAuth) cacheFlush(ctx context.Context, tenantID string) error {
1✔
1709
        if d.cache == nil {
1✔
UNCOV
1710
                return nil
×
UNCOV
1711
        }
×
1712

1713
        return d.cache.SuspendTenant(ctx, tenantID)
1✔
1714
}
1715

1716
func (d *DevAuth) GetTenantDeviceStatus(
1717
        ctx context.Context,
1718
        tenantId,
1719
        deviceId string,
1720
) (*model.Status, error) {
1✔
1721
        if tenantId != "" {
2✔
1722
                ctx = identity.WithContext(ctx, &identity.Identity{
1✔
1723
                        Tenant: tenantId,
1✔
1724
                })
1✔
1725
        }
1✔
1726
        dev, err := d.db.GetDeviceById(ctx, deviceId)
1✔
1727
        switch err {
1✔
1728
        case nil:
1✔
1729
                return &model.Status{Status: dev.Status}, nil
1✔
1730
        case store.ErrDevNotFound:
1✔
1731
                return nil, ErrDeviceNotFound
1✔
1732
        default:
1✔
1733
                return nil, errors.Wrapf(err, "get device %s failed", deviceId)
1✔
1734

1735
        }
1736
}
1737

1738
func (d *DevAuth) updateCheckInTime(
1739
        ctx context.Context,
1740
        deviceId string,
1741
        tenantId string,
1742
        previous *time.Time,
1743
) {
1✔
1744
        var err error
1✔
1745
        defer func() {
2✔
1746
                if err != nil {
1✔
UNCOV
1747
                        log.FromContext(ctx).Errorf(
×
UNCOV
1748
                                "failed to update device check-in time for device %s: %s",
×
UNCOV
1749
                                deviceId, err.Error(),
×
1750
                        )
×
1751
                }
×
1752
        }()
1753
        checkInTime := uto.TimePtr(time.Now().UTC())
1✔
1754
        // in case cache is disabled, use mongo
1✔
1755
        if d.cache == nil {
2✔
1756
                if err = d.db.UpdateDevice(ctx,
1✔
1757
                        deviceId,
1✔
1758
                        model.DeviceUpdate{
1✔
1759
                                CheckInTime: checkInTime,
1✔
1760
                        }); err != nil {
1✔
UNCOV
1761
                        return
×
UNCOV
1762
                }
×
1763
        } else {
1✔
1764
                // get check-in time from cache
1✔
1765
                previous, err = d.cache.GetCheckInTime(ctx, tenantId, deviceId)
1✔
1766
                if err != nil {
1✔
UNCOV
1767
                        return
×
UNCOV
1768
                }
×
1769
                // update check-in time in cache
1770
                err = d.cache.CacheCheckInTime(ctx, checkInTime, tenantId, deviceId)
1✔
1771
                if err != nil {
1✔
UNCOV
1772
                        return
×
UNCOV
1773
                }
×
1774
        }
1775
        // compare data without a time of current and previous check-in time
1776
        // and if it's different trigger reindexing (if enabled)
1777
        // and save check-in time in the database
1778
        if previous == nil ||
1✔
1779
                (previous != nil &&
1✔
1780
                        !previous.Truncate(24*time.Hour).Equal(checkInTime.Truncate(24*time.Hour))) {
2✔
1781
                // trigger reindexing
1✔
1782
                if d.config.EnableReporting {
1✔
UNCOV
1783
                        if err = d.cOrch.SubmitReindexReporting(ctx, deviceId); err != nil {
×
UNCOV
1784
                                err = errors.Wrap(err, "reindex reporting job error")
×
UNCOV
1785
                                return
×
1786
                        }
×
1787
                } else {
1✔
1788
                        // update check-in time in inventory
1✔
1789
                        if err := d.syncCheckInTime(ctx, checkInTime, deviceId, tenantId); err != nil {
1✔
UNCOV
1790
                                log.FromContext(ctx).Errorf(
×
UNCOV
1791
                                        "failed to synchronize device check-in time with inventory: device %s: %s",
×
UNCOV
1792
                                        deviceId, err.Error(),
×
1793
                                )
×
1794
                        }
×
1795
                }
1796
                // dump cached value to database
1797
                if d.cache != nil {
2✔
1798
                        if err = d.db.UpdateDevice(ctx,
1✔
1799
                                deviceId,
1✔
1800
                                model.DeviceUpdate{
1✔
1801
                                        CheckInTime: checkInTime,
1✔
1802
                                }); err != nil {
1✔
UNCOV
1803
                                return
×
UNCOV
1804
                        }
×
1805
                }
1806
        }
1807
}
1808

1809
func (d *DevAuth) syncCheckInTime(
1810
        ctx context.Context,
1811
        checkInTime *time.Time,
1812
        deviceId string,
1813
        tenantId string,
1814
) error {
1✔
1815
        attributes := []model.DeviceAttribute{
1✔
1816
                {
1✔
1817
                        Name:        "check_in_time",
1✔
1818
                        Description: nil,
1✔
1819
                        Value:       checkInTime,
1✔
1820
                        Scope:       InventoryScopeSystem,
1✔
1821
                },
1✔
1822
        }
1✔
1823
        attrJson, err := json.Marshal(attributes)
1✔
1824
        if err != nil {
1✔
UNCOV
1825
                return errors.New("internal error: cannot marshal attributes into json")
×
UNCOV
1826
        }
×
1827
        if err := d.cOrch.SubmitUpdateDeviceInventoryJob(
1✔
1828
                ctx,
1✔
1829
                orchestrator.UpdateDeviceInventoryReq{
1✔
1830
                        RequestId:  requestid.FromContext(ctx),
1✔
1831
                        TenantId:   tenantId,
1✔
1832
                        DeviceId:   deviceId,
1✔
1833
                        Scope:      InventoryScopeSystem,
1✔
1834
                        Attributes: string(attrJson),
1✔
1835
                }); err != nil {
1✔
UNCOV
1836
                return errors.Wrap(err, "failed to start device inventory update job")
×
UNCOV
1837
        }
×
1838
        return nil
1✔
1839
}
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