• 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

89.53
/store/mongo/datastore_mongo.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package mongo
16

17
import (
18
        "context"
19
        "crypto/tls"
20
        "strings"
21
        "time"
22

23
        "github.com/mendersoftware/go-lib-micro/identity"
24
        "github.com/mendersoftware/go-lib-micro/log"
25
        "github.com/mendersoftware/go-lib-micro/mongo/migrate"
26
        "github.com/mendersoftware/go-lib-micro/mongo/oid"
27
        ctxstore "github.com/mendersoftware/go-lib-micro/store/v2"
28
        "github.com/pkg/errors"
29
        "go.mongodb.org/mongo-driver/bson"
30
        "go.mongodb.org/mongo-driver/mongo"
31
        mopts "go.mongodb.org/mongo-driver/mongo/options"
32

33
        "github.com/mendersoftware/deviceauth/jwt"
34
        "github.com/mendersoftware/deviceauth/model"
35
        "github.com/mendersoftware/deviceauth/store"
36
        uto "github.com/mendersoftware/deviceauth/utils/to"
37
)
38

39
const (
40
        DbVersion     = "2.0.0"
41
        DbName        = "deviceauth"
42
        DbDevicesColl = "devices"
43
        DbAuthSetColl = "auth_sets"
44
        DbTokensColl  = "tokens"
45
        DbLimitsColl  = "limits"
46

47
        DbKeyDeviceRevision = "revision"
48
        dbFieldID           = "_id"
49
        dbFieldTenantID     = "tenant_id"
50
        dbFieldIDDataSha    = "id_data_sha256"
51
        dbFieldStatus       = "status"
52
        dbFieldDeviceID     = "device_id"
53
        dbFieldPubKey       = "pubkey"
54
        dbFieldExpTime      = "exp.time"
55
        dbFieldTenantClaim  = "mender.tenant"
56
        dbFieldName         = "name"
57
        dbFieldSubject      = "sub"
58
)
59

60
var (
61
        indexDevices_IdentityData                       = "devices:IdentityData"
62
        indexDevices_IdentityDataSha256                 = "devices:IdentityDataSha256"
63
        indexDevices_Status                             = "devices:Status"
64
        indexAuthSet_DeviceId_IdentityData_PubKey       = "auth_sets:DeviceId:IdData:PubKey"
65
        indexAuthSet_DeviceId_IdentityDataSha256_PubKey = "auth_sets:IdDataSha256:PubKey"
66
        indexAuthSet_IdentityDataSha256_PubKey          = "auth_sets:NoDeviceId:IdDataSha256:PubKey"
67
)
68

69
type DataStoreMongoConfig struct {
70
        // connection string
71
        ConnectionString string
72

73
        // SSL support
74
        SSL           bool
75
        SSLSkipVerify bool
76

77
        // Overwrites credentials provided in connection string if provided
78
        Username string
79
        Password string
80
}
81

82
type DataStoreMongo struct {
83
        client      *mongo.Client
84
        automigrate bool
85
        multitenant bool
86
}
87

88
func NewDataStoreMongoWithClient(client *mongo.Client) *DataStoreMongo {
89✔
89
        return &DataStoreMongo{
89✔
90
                client: client,
89✔
91
        }
89✔
92
}
89✔
93

94
func NewDataStoreMongo(config DataStoreMongoConfig) (*DataStoreMongo, error) {
1✔
95
        if !strings.Contains(config.ConnectionString, "://") {
2✔
96
                config.ConnectionString = "mongodb://" + config.ConnectionString
1✔
97
        }
1✔
98
        clientOptions := mopts.Client().ApplyURI(config.ConnectionString)
1✔
99

1✔
100
        if config.Username != "" {
1✔
101
                clientOptions.SetAuth(mopts.Credential{
×
102
                        Username: config.Username,
×
103
                        Password: config.Password,
×
104
                })
×
105
        }
×
106

107
        if config.SSL {
1✔
108
                tlsConfig := &tls.Config{}
×
109
                tlsConfig.InsecureSkipVerify = config.SSLSkipVerify
×
110
                clientOptions.SetTLSConfig(tlsConfig)
×
111
        }
×
112
        ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
1✔
113
        defer cancel()
1✔
114

1✔
115
        client, err := mongo.Connect(ctx, clientOptions)
1✔
116
        if err != nil {
1✔
117
                return nil, errors.Wrap(err, "failed to create mongo client")
×
118
        }
×
119

120
        err = client.Ping(ctx, nil)
1✔
121
        if err != nil {
1✔
122
                return nil, errors.Wrap(err, "failed to verify mongodb connection")
×
123
        }
×
124

125
        return NewDataStoreMongoWithClient(client), nil
1✔
126
}
127

128
func (db *DataStoreMongo) ForEachTenant(
129
        ctx context.Context,
130
        mapFunc store.MapFunc,
131
) error {
4✔
132
        var (
4✔
133
                dbCtx   context.Context
4✔
134
                err     error
4✔
135
                errChan = make(chan error, 1)
4✔
136
        )
4✔
137
        tenantsIds, err := db.ListTenantsIds(ctx)
4✔
138
        if err != nil {
4✔
139
                return errors.Wrap(err, "store: failed to retrieve tenants")
×
140
        }
×
141
        go func() {
8✔
142
                for _, tenantID := range tenantsIds {
10✔
143
                        if ctx.Err() != nil {
7✔
144
                                return
1✔
145
                        }
1✔
146
                        if tenantID != "" {
9✔
147
                                dbCtx = identity.WithContext(ctx,
4✔
148
                                        &identity.Identity{
4✔
149
                                                Tenant: tenantID,
4✔
150
                                        },
4✔
151
                                )
4✔
152
                        } else {
5✔
153
                                dbCtx = ctx
1✔
154
                        }
1✔
155
                        err := mapFunc(dbCtx)
5✔
156
                        if err != nil {
6✔
157
                                errChan <- errors.Wrapf(err,
1✔
158
                                        `store: failed to apply mapFunc to tenant "%s"`,
1✔
159
                                        tenantID,
1✔
160
                                )
1✔
161
                        }
1✔
162
                }
163
                errChan <- nil
3✔
164
        }()
165

166
        select {
4✔
167
        case err = <-errChan:
2✔
168
        case <-ctx.Done():
2✔
169
                err = errors.Wrap(ctx.Err(),
2✔
170
                        "store: database operations stopped prematurely",
2✔
171
                )
2✔
172
        }
173
        return err
4✔
174
}
175

176
func (db *DataStoreMongo) Ping(ctx context.Context) error {
1✔
177
        return db.client.Ping(ctx, nil)
1✔
178
}
1✔
179

180
type DeviceFilter model.DeviceFilter
181

182
func (fltr DeviceFilter) MarshalBSON() (b []byte, err error) {
12✔
183
        doc := bson.D{}
12✔
184
        switch len(fltr.IDs) {
12✔
185
        case 0:
10✔
186
                break
10✔
187
        case 1:
1✔
188
                doc = append(doc, bson.E{Key: "_id", Value: fltr.IDs[0]})
1✔
189
        default:
1✔
190
                doc = append(doc, bson.E{
1✔
191
                        Key: "_id", Value: bson.D{{
1✔
192
                                Key: "$in", Value: fltr.IDs,
1✔
193
                        }},
1✔
194
                })
1✔
195
        }
196
        switch len(fltr.Status) {
12✔
197
        case 0:
7✔
198
                break
7✔
199
        case 1:
4✔
200
                doc = append(doc, bson.E{Key: "status", Value: fltr.Status[0]})
4✔
201
        default:
1✔
202
                doc = append(doc, bson.E{
1✔
203
                        Key: "status", Value: bson.D{{
1✔
204
                                Key: "$in", Value: fltr.Status,
1✔
205
                        }},
1✔
206
                })
1✔
207
        }
208

209
        return bson.Marshal(doc)
12✔
210
}
211

212
func (db *DataStoreMongo) GetDevices(
213
        ctx context.Context,
214
        skip,
215
        limit uint,
216
        filter model.DeviceFilter,
217
) ([]model.Device, error) {
12✔
218
        const MaxInt64 = int64(^uint64(1 << 63))
12✔
219
        var (
12✔
220
                res  = []model.Device{}
12✔
221
                fltr = DeviceFilter(filter)
12✔
222
        )
12✔
223
        collDevs := db.client.
12✔
224
                Database(DbName).
12✔
225
                Collection(DbDevicesColl)
12✔
226

12✔
227
        findOpts := mopts.Find().
12✔
228
                SetSort(bson.D{
12✔
229
                        {Key: dbFieldStatus, Value: 1},
12✔
230
                        {Key: dbFieldID, Value: 1},
12✔
231
                }).
12✔
232
                SetSkip(int64(skip) & MaxInt64)
12✔
233

12✔
234
        if limit > 0 {
24✔
235
                findOpts.SetLimit(int64(limit))
12✔
236
        }
12✔
237

238
        cursor, err := collDevs.Find(ctx, ctxstore.WithTenantID(ctx, fltr), findOpts)
12✔
239
        if err != nil {
12✔
240
                return nil, errors.Wrap(err, "failed to fetch device list")
×
241
        }
×
242
        if err := cursor.All(ctx, &res); err != nil {
12✔
243
                return nil, err
×
244
        }
×
245

246
        return res, nil
12✔
247
}
248

249
func (db *DataStoreMongo) StoreMigrationVersion(
250
        ctx context.Context,
251
        version *migrate.Version,
252
) error {
2✔
253
        if version == nil {
3✔
254
                return errors.New("version cant be nil.")
1✔
255
        }
1✔
256

257
        c := db.client.Database(DbName).
1✔
258
                Collection(migrate.DbMigrationsColl)
1✔
259

1✔
260
        migrationInfo := migrate.MigrationEntry{
1✔
261
                Version:   *version,
1✔
262
                Timestamp: time.Now(),
1✔
263
        }
1✔
264
        _, err := c.InsertOne(ctx, migrationInfo)
1✔
265
        return err
1✔
266
}
267

268
func (db *DataStoreMongo) GetDeviceById(ctx context.Context, id string) (*model.Device, error) {
5✔
269

5✔
270
        c := db.client.Database(DbName).Collection(DbDevicesColl)
5✔
271

5✔
272
        res := model.Device{}
5✔
273

5✔
274
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": id})).Decode(&res)
5✔
275

5✔
276
        if err != nil {
7✔
277
                if err == mongo.ErrNoDocuments {
4✔
278
                        return nil, store.ErrDevNotFound
2✔
279
                } else {
2✔
280
                        return nil, errors.Wrap(err, "failed to fetch device")
×
281
                }
×
282
        }
283

284
        return &res, nil
4✔
285
}
286

287
func (db *DataStoreMongo) GetDeviceByIdentityDataHash(
288
        ctx context.Context,
289
        idataHash []byte,
290
) (*model.Device, error) {
6✔
291
        c := db.client.Database(DbName).Collection(DbDevicesColl)
6✔
292

6✔
293
        filter := ctxstore.WithTenantID(ctx, bson.M{dbFieldIDDataSha: idataHash})
6✔
294
        res := model.Device{}
6✔
295

6✔
296
        err := c.FindOne(ctx, filter).Decode(&res)
6✔
297
        if err != nil {
8✔
298
                if err == mongo.ErrNoDocuments {
4✔
299
                        return nil, store.ErrDevNotFound
2✔
300
                } else {
2✔
301
                        return nil, errors.Wrap(err, "failed to fetch device")
×
302
                }
×
303
        }
304

305
        return &res, nil
4✔
306
}
307

308
func (db *DataStoreMongo) AddDevice(ctx context.Context, d model.Device) error {
347✔
309

347✔
310
        if d.Id == "" {
349✔
311
                uid := oid.NewUUIDv4()
2✔
312
                d.Id = uid.String()
2✔
313
        }
2✔
314

315
        id := identity.FromContext(ctx)
347✔
316
        tenantId := ""
347✔
317
        if id != nil {
693✔
318
                tenantId = id.Tenant
346✔
319
        }
346✔
320
        d.TenantID = tenantId
347✔
321

347✔
322
        c := db.client.Database(DbName).Collection(DbDevicesColl)
347✔
323

347✔
324
        if _, err := c.InsertOne(ctx, d); err != nil {
350✔
325
                if strings.Contains(err.Error(), "duplicate key error") {
6✔
326
                        return store.ErrObjectExists
3✔
327
                }
3✔
328
                return errors.Wrap(err, "failed to store device")
×
329
        }
330
        return nil
345✔
331
}
332

333
func (db *DataStoreMongo) UpdateDevice(ctx context.Context,
334
        deviceID string, updev model.DeviceUpdate) error {
5✔
335

5✔
336
        c := db.client.Database(DbName).Collection(DbDevicesColl)
5✔
337

5✔
338
        updev.UpdatedTs = uto.TimePtr(time.Now().UTC())
5✔
339
        update := bson.M{
5✔
340
                "$inc": bson.M{
5✔
341
                        DbKeyDeviceRevision: 1,
5✔
342
                },
5✔
343
                "$set": updev,
5✔
344
        }
5✔
345

5✔
346
        res, err := c.UpdateOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": deviceID}), update)
5✔
347
        if err != nil {
5✔
348
                return errors.Wrap(err, "failed to update device")
×
349
        } else if res.MatchedCount < 1 {
9✔
350
                return store.ErrDevNotFound
4✔
351
        }
4✔
352

353
        return nil
2✔
354
}
355

356
func (db *DataStoreMongo) DeleteDevice(ctx context.Context, id string) error {
5✔
357

5✔
358
        c := db.client.Database(DbName).Collection(DbDevicesColl)
5✔
359

5✔
360
        filter := ctxstore.WithTenantID(ctx, bson.M{"_id": id})
5✔
361
        result, err := c.DeleteOne(ctx, filter)
5✔
362
        if err != nil {
5✔
363
                return errors.Wrap(err, "failed to remove device")
×
364
        } else if result.DeletedCount < 1 {
8✔
365
                return store.ErrDevNotFound
3✔
366
        }
3✔
367

368
        return nil
2✔
369
}
370

371
func (db *DataStoreMongo) AddToken(ctx context.Context, t *jwt.Token) error {
2✔
372
        database := db.client.Database(DbName)
2✔
373
        collTokens := database.Collection(DbTokensColl)
2✔
374

2✔
375
        filter := bson.M{"_id": t.Claims.ID}
2✔
376
        update := bson.M{"$set": t}
2✔
377
        updateOpts := mopts.Update()
2✔
378
        updateOpts.SetUpsert(true)
2✔
379

2✔
380
        if _, err := collTokens.UpdateOne(
2✔
381
                ctx, filter, update, updateOpts,
2✔
382
        ); err != nil {
2✔
383
                return errors.Wrap(err, "failed to store token")
×
384
        }
×
385

386
        return nil
2✔
387
}
388

389
func (db *DataStoreMongo) GetToken(
390
        ctx context.Context,
391
        jti oid.ObjectID,
392
) (*jwt.Token, error) {
5✔
393

5✔
394
        c := db.client.Database(DbName).Collection(DbTokensColl)
5✔
395

5✔
396
        res := jwt.Token{}
5✔
397

5✔
398
        err := c.FindOne(ctx, bson.M{"_id": jti}).Decode(&res)
5✔
399
        if err != nil {
8✔
400
                if err == mongo.ErrNoDocuments {
6✔
401
                        return nil, store.ErrTokenNotFound
3✔
402
                } else {
3✔
403
                        return nil, errors.Wrap(err, "failed to fetch token")
×
404
                }
×
405
        }
406

407
        return &res, nil
3✔
408
}
409

410
func (db *DataStoreMongo) DeleteToken(ctx context.Context, jti oid.ObjectID) error {
5✔
411
        database := db.client.Database(DbName)
5✔
412
        collTokens := database.Collection(DbTokensColl)
5✔
413

5✔
414
        filter := bson.M{"_id": jti}
5✔
415
        result, err := collTokens.DeleteOne(ctx, filter)
5✔
416
        if err != nil {
5✔
417
                return errors.Wrap(err, "failed to remove token")
×
418
        } else if result.DeletedCount < 1 {
7✔
419
                return store.ErrTokenNotFound
2✔
420
        }
2✔
421

422
        return nil
3✔
423
}
424

425
func (db *DataStoreMongo) DeleteTokens(ctx context.Context) error {
3✔
426
        c := db.client.Database(DbName).
3✔
427
                Collection(DbTokensColl)
3✔
428

3✔
429
        id := identity.FromContext(ctx)
3✔
430
        filter := bson.M{dbFieldTenantClaim: nil}
3✔
431
        if id != nil && id.Tenant != "" {
4✔
432
                filter[dbFieldTenantClaim] = id.Tenant
1✔
433
        }
1✔
434
        _, err := c.DeleteMany(ctx, filter)
3✔
435

3✔
436
        return err
3✔
437
}
438

439
func (db *DataStoreMongo) DeleteTokenByDevId(
440
        ctx context.Context,
441
        devID oid.ObjectID,
442
) error {
4✔
443
        c := db.client.Database(DbName).
4✔
444
                Collection(DbTokensColl)
4✔
445
        filter := bson.M{"sub": devID, dbFieldTenantClaim: nil}
4✔
446
        id := identity.FromContext(ctx)
4✔
447
        if id != nil && id.Tenant != "" {
6✔
448
                filter[dbFieldTenantClaim] = id.Tenant
2✔
449
        }
2✔
450
        ci, err := c.DeleteMany(ctx, filter)
4✔
451

4✔
452
        if err != nil {
4✔
453
                return errors.Wrap(err, "failed to remove tokens")
×
454
        }
×
455

456
        if ci.DeletedCount == 0 {
6✔
457
                return store.ErrTokenNotFound
2✔
458
        }
2✔
459

460
        return nil
3✔
461
}
462

463
func (db *DataStoreMongo) Migrate(ctx context.Context, version string) error {
72✔
464
        l := log.FromContext(ctx)
72✔
465

72✔
466
        dbs := []string{DbName}
72✔
467

72✔
468
        if db.multitenant {
74✔
469
                l.Infof("running migrations in multitenant mode")
2✔
470

2✔
471
                tdbs, err := migrate.GetTenantDbs(ctx, db.client, ctxstore.IsTenantDb(DbName))
2✔
472
                if err != nil {
2✔
473
                        return errors.Wrap(err, "failed go retrieve tenant DBs")
×
474
                }
×
475
                dbs = append(dbs, tdbs...)
2✔
476
        } else {
70✔
477
                l.Infof("running migrations in single tenant mode")
70✔
478
        }
70✔
479

480
        if db.automigrate {
142✔
481
                l.Infof("automigrate is ON, will apply migrations")
70✔
482
        } else {
72✔
483
                l.Infof("automigrate is OFF, will check db version compatibility")
2✔
484
        }
2✔
485

486
        for _, d := range dbs {
146✔
487
                // if not in multi tenant, then tenant will be "" and identity
74✔
488
                // will be the same as default
74✔
489
                tenant := ctxstore.TenantFromDbName(d, DbName)
74✔
490

74✔
491
                tenantCtx := identity.WithContext(ctx, &identity.Identity{
74✔
492
                        Tenant: tenant,
74✔
493
                })
74✔
494

74✔
495
                // TODO: we should aim to unify context usage across migrators and migrations
74✔
496
                // migrators use 'string' db name, migrations use DbFromContext
74✔
497
                // both should use one or the other; for now - just redundantly pass both ctx and db name
74✔
498
                err := db.MigrateTenant(tenantCtx, d, version)
74✔
499
                if err != nil {
77✔
500
                        return err
3✔
501
                }
3✔
502
        }
503

504
        return nil
69✔
505
}
506

507
func (db *DataStoreMongo) MigrateTenant(ctx context.Context, database, version string) error {
75✔
508
        l := log.FromContext(ctx)
75✔
509

75✔
510
        l.Infof("migrating %s", database)
75✔
511

75✔
512
        m := migrate.SimpleMigrator{
75✔
513
                Client:      db.client,
75✔
514
                Db:          database,
75✔
515
                Automigrate: db.automigrate,
75✔
516
        }
75✔
517

75✔
518
        migrations := []migrate.Migration{
75✔
519
                &migration_1_1_0{
75✔
520
                        ms:  db,
75✔
521
                        ctx: ctx,
75✔
522
                },
75✔
523
                &migration_1_2_0{
75✔
524
                        ms:  db,
75✔
525
                        ctx: ctx,
75✔
526
                },
75✔
527
                &migration_1_3_0{
75✔
528
                        ms:  db,
75✔
529
                        ctx: ctx,
75✔
530
                },
75✔
531
                &migration_1_4_0{
75✔
532
                        ms:  db,
75✔
533
                        ctx: ctx,
75✔
534
                },
75✔
535
                &migration_1_5_0{
75✔
536
                        ms:  db,
75✔
537
                        ctx: ctx,
75✔
538
                },
75✔
539
                &migration_1_6_0{
75✔
540
                        ms:  db,
75✔
541
                        ctx: ctx,
75✔
542
                },
75✔
543
                &migration_1_7_0{
75✔
544
                        ms:  db,
75✔
545
                        ctx: ctx,
75✔
546
                },
75✔
547
                &migration_1_8_0{
75✔
548
                        ds:  db,
75✔
549
                        ctx: ctx,
75✔
550
                },
75✔
551
                &migration_1_9_0{
75✔
552
                        ds:  db,
75✔
553
                        ctx: ctx,
75✔
554
                },
75✔
555
                &migration_1_10_0{
75✔
556
                        ds:  db,
75✔
557
                        ctx: ctx,
75✔
558
                },
75✔
559
                &migration_1_11_0{
75✔
560
                        ds:  db,
75✔
561
                        ctx: ctx,
75✔
562
                },
75✔
563
                &migration_2_0_0{
75✔
564
                        ds:  db,
75✔
565
                        ctx: ctx,
75✔
566
                },
75✔
567
        }
75✔
568

75✔
569
        ver, err := migrate.NewVersion(version)
75✔
570
        if err != nil {
76✔
571
                return errors.Wrap(err, "failed to parse service version")
1✔
572
        }
1✔
573

574
        err = m.Apply(ctx, *ver, migrations)
74✔
575
        if err != nil {
76✔
576
                return errors.Wrap(err, "failed to apply migrations")
2✔
577
        }
2✔
578

579
        return nil
72✔
580
}
581

582
func fillAuthSet(ctx context.Context, authSet *model.AuthSet) {
601✔
583
        if authSet.Id == "" {
1,200✔
584
                uid := oid.NewUUIDv4()
599✔
585
                authSet.Id = uid.String()
599✔
586
        }
599✔
587
        id := identity.FromContext(ctx)
601✔
588
        tenantId := ""
601✔
589
        if id != nil {
1,201✔
590
                tenantId = id.Tenant
600✔
591
        }
600✔
592
        authSet.TenantID = tenantId
601✔
593
        if authSet.Timestamp == nil {
603✔
594
                ts := time.Now()
2✔
595
                authSet.Timestamp = &ts
2✔
596
        }
2✔
597
}
598

599
func (db *DataStoreMongo) AddAuthSet(ctx context.Context, set model.AuthSet) error {
599✔
600
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
599✔
601
        fillAuthSet(ctx, &set)
599✔
602

599✔
603
        if _, err := c.InsertOne(ctx, set); err != nil {
600✔
604
                if mongo.IsDuplicateKeyError(err) {
2✔
605
                        return store.ErrObjectExists
1✔
606
                }
1✔
NEW
607
                return errors.Wrap(err, "failed to store device")
×
608
        }
609

610
        return nil
599✔
611
}
612

613
// UpsertAuthSetStatus inserts a new auth set and if it exists, ensures the
614
// AuthSet.Status matches the one in authSet.
615
func (db *DataStoreMongo) UpsertAuthSetStatus(ctx context.Context, authSet *model.AuthSet) error {
2✔
616
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
2✔
617

2✔
618
        fillAuthSet(ctx, authSet)
2✔
619

2✔
620
        fltr := bson.D{
2✔
621
                {Key: dbFieldTenantID, Value: authSet.TenantID},
2✔
622
                {Key: dbFieldIDDataSha, Value: authSet.IdDataSha256},
2✔
623
                {Key: dbFieldPubKey, Value: authSet.PubKey},
2✔
624
        }
2✔
625
        setOnInsert := *authSet
2✔
626
        setOnInsert.Status = ""
2✔
627

2✔
628
        update := bson.D{
2✔
629
                {Key: "$setOnInsert", Value: setOnInsert},
2✔
630
                {Key: "$set", Value: bson.D{
2✔
631
                        {Key: dbFieldStatus, Value: authSet.Status},
2✔
632
                }},
2✔
633
        }
2✔
634
        _, err := c.UpdateOne(ctx, fltr, update, mopts.Update().SetUpsert(true))
2✔
635
        if err != nil {
2✔
NEW
636
                if mongo.IsDuplicateKeyError(err) {
×
UNCOV
637
                        return store.ErrObjectExists
×
UNCOV
638
                }
×
639
                return errors.Wrap(err, "failed to store device")
×
640
        }
641

642
        return nil
2✔
643
}
644

645
func (db *DataStoreMongo) GetAuthSetByIdDataHashKey(
646
        ctx context.Context,
647
        idDataHash []byte,
648
        key string,
649
) (*model.AuthSet, error) {
6✔
650
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
6✔
651

6✔
652
        id := identity.FromContext(ctx)
6✔
653
        tenantId := ""
6✔
654
        if id != nil {
10✔
655
                tenantId = id.Tenant
4✔
656
        }
4✔
657

658
        filter := model.AuthSet{
6✔
659
                IdDataSha256: idDataHash,
6✔
660
                PubKey:       key,
6✔
661
                TenantID:     tenantId,
6✔
662
        }
6✔
663
        res := model.AuthSet{}
6✔
664

6✔
665
        err := c.FindOne(ctx, filter).Decode(&res)
6✔
666
        if err != nil {
8✔
667
                if err == mongo.ErrNoDocuments {
4✔
668
                        return nil, store.ErrAuthSetNotFound
2✔
669
                } else {
2✔
670
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
671
                }
×
672
        }
673

674
        return &res, nil
4✔
675
}
676

677
func (db *DataStoreMongo) GetAuthSetByIdDataHashKeyByStatus(
678
        ctx context.Context,
679
        idDataHash []byte,
680
        key string,
681
        status string,
682
) (*model.AuthSet, error) {
3✔
683
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
3✔
684

3✔
685
        id := identity.FromContext(ctx)
3✔
686
        tenantId := ""
3✔
687
        if id != nil {
5✔
688
                tenantId = id.Tenant
2✔
689
        }
2✔
690

691
        filter := model.AuthSet{
3✔
692
                IdDataSha256: idDataHash,
3✔
693
                PubKey:       key,
3✔
694
                TenantID:     tenantId,
3✔
695
                Status:       status,
3✔
696
        }
3✔
697
        res := model.AuthSet{}
3✔
698

3✔
699
        err := c.FindOne(ctx, filter).Decode(&res)
3✔
700
        if err != nil {
5✔
701
                if err == mongo.ErrNoDocuments {
4✔
702
                        return nil, store.ErrAuthSetNotFound
2✔
703
                } else {
2✔
704
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
705
                }
×
706
        }
707

708
        return &res, nil
1✔
709
}
710

711
func (db *DataStoreMongo) GetAuthSetById(
712
        ctx context.Context,
713
        auth_id string,
714
) (*model.AuthSet, error) {
4✔
715
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
4✔
716

4✔
717
        res := model.AuthSet{}
4✔
718
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": auth_id})).Decode(&res)
4✔
719
        if err != nil {
6✔
720
                if err == mongo.ErrNoDocuments {
4✔
721
                        return nil, store.ErrAuthSetNotFound
2✔
722
                } else {
2✔
723
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
724
                }
×
725
        }
726

727
        return &res, nil
3✔
728
}
729

730
func (db *DataStoreMongo) GetAuthSetsForDevice(
731
        ctx context.Context,
732
        devid string,
733
) ([]model.AuthSet, error) {
6✔
734
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
6✔
735

6✔
736
        res := []model.AuthSet{}
6✔
737

6✔
738
        id := identity.FromContext(ctx)
6✔
739
        tenantId := ""
6✔
740
        if id != nil {
11✔
741
                tenantId = id.Tenant
5✔
742
        }
5✔
743
        cursor, err := c.Find(ctx, model.AuthSet{DeviceId: devid, TenantID: tenantId})
6✔
744
        if err != nil {
6✔
745
                return nil, err
×
746
        }
×
747
        if err = cursor.All(ctx, &res); err != nil {
6✔
748
                if err == mongo.ErrNoDocuments {
×
749
                        return nil, store.ErrAuthSetNotFound
×
750
                }
×
751
                return nil, errors.Wrap(err, "failed to fetch authentication sets")
×
752
        }
753

754
        return res, nil
6✔
755
}
756

757
func (db *DataStoreMongo) RejectAuthSetsForDevice(
758
        ctx context.Context,
759
        deviceID string,
760
) error {
1✔
761
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
1✔
762
        var tenantID string
1✔
763
        if id := identity.FromContext(ctx); id != nil {
1✔
764
                tenantID = id.Tenant
×
765
        }
×
766

767
        filter := bson.M{
1✔
768
                model.AuthSetKeyDeviceId: deviceID,
1✔
769
                model.AuthSetKeyStatus:   model.DevStatusAccepted,
1✔
770
                dbFieldTenantID:          tenantID,
1✔
771
        }
1✔
772
        update := bson.M{
1✔
773
                "$set": bson.M{
1✔
774
                        model.AuthSetKeyStatus: model.DevStatusRejected,
1✔
775
                },
1✔
776
        }
1✔
777

1✔
778
        if res, err := c.UpdateMany(ctx, filter, update); err != nil {
1✔
779
                return errors.Wrap(err, "failed to update auth set")
×
780
        } else if res.MatchedCount == 0 {
2✔
781
                return store.ErrAuthSetNotFound
1✔
782
        }
1✔
783

784
        return nil
×
785
}
786

787
func (db *DataStoreMongo) UpdateAuthSet(
788
        ctx context.Context,
789
        filter interface{},
790
        mod model.AuthSetUpdate,
791
) error {
6✔
792
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
6✔
793

6✔
794
        update := bson.M{"$set": mod}
6✔
795

6✔
796
        if res, err := c.UpdateMany(ctx, ctxstore.WithTenantID(ctx, filter), update); err != nil {
6✔
797
                return errors.Wrap(err, "failed to update auth set")
×
798
        } else if res.MatchedCount == 0 {
8✔
799
                return store.ErrAuthSetNotFound
2✔
800
        }
2✔
801

802
        return nil
4✔
803
}
804

805
func (db *DataStoreMongo) UpdateAuthSetById(
806
        ctx context.Context,
807
        id string,
808
        mod model.AuthSetUpdate,
809
) error {
6✔
810
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
6✔
811
        res, err := c.UpdateOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": id}), bson.M{"$set": mod})
6✔
812
        if err != nil {
6✔
813
                return errors.Wrap(err, "failed to update auth set")
×
814
        }
×
815
        if res.MatchedCount == 0 {
7✔
816
                return store.ErrAuthSetNotFound
1✔
817
        }
1✔
818

819
        return nil
5✔
820
}
821

822
func (db *DataStoreMongo) DeleteAuthSetsForDevice(ctx context.Context, devid string) error {
4✔
823
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
4✔
824
        id := identity.FromContext(ctx)
4✔
825
        tenantId := ""
4✔
826
        if id != nil {
7✔
827
                tenantId = id.Tenant
3✔
828
        }
3✔
829

830
        ci, err := c.DeleteMany(
4✔
831
                ctx,
4✔
832
                model.AuthSet{
4✔
833
                        DeviceId: devid,
4✔
834
                        TenantID: tenantId,
4✔
835
                },
4✔
836
        )
4✔
837

4✔
838
        if err != nil {
4✔
839
                return errors.Wrap(err, "failed to remove authentication sets for device")
×
840
        }
×
841

842
        if ci.DeletedCount == 0 {
5✔
843
                return store.ErrAuthSetNotFound
1✔
844
        }
1✔
845

846
        return nil
3✔
847
}
848

849
func (db *DataStoreMongo) DeleteAuthSetForDevice(
850
        ctx context.Context,
851
        devId string,
852
        authId string,
853
) error {
4✔
854
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
4✔
855

4✔
856
        id := identity.FromContext(ctx)
4✔
857
        tenantId := ""
4✔
858
        if id != nil {
6✔
859
                tenantId = id.Tenant
2✔
860
        }
2✔
861
        filter := model.AuthSet{Id: authId, DeviceId: devId, TenantID: tenantId}
4✔
862
        result, err := c.DeleteOne(ctx, filter)
4✔
863
        if err != nil {
4✔
864
                return errors.Wrap(err, "failed to remove authentication set for device")
×
865
        } else if result.DeletedCount < 1 {
6✔
866
                return store.ErrAuthSetNotFound
2✔
867
        }
2✔
868

869
        return nil
2✔
870
}
871

872
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
2✔
873
        db.multitenant = true
2✔
874
        return db
2✔
875
}
2✔
876

877
func (db *DataStoreMongo) WithAutomigrate() store.DataStore {
70✔
878
        db.automigrate = true
70✔
879
        return db
70✔
880
}
70✔
881

882
func (db *DataStoreMongo) PutLimit(ctx context.Context, lim model.Limit) error {
5✔
883
        if lim.Name == "" {
6✔
884
                return errors.New("empty limit name")
1✔
885
        }
1✔
886

887
        c := db.client.Database(DbName).Collection(DbLimitsColl)
4✔
888
        id := identity.FromContext(ctx)
4✔
889
        tenantId := ""
4✔
890
        if id != nil {
8✔
891
                tenantId = id.Tenant
4✔
892
        }
4✔
893

894
        lim.TenantID = tenantId
4✔
895
        query := ctxstore.WithTenantID(ctx, bson.M{dbFieldName: lim.Name})
4✔
896

4✔
897
        updateOptions := mopts.Update()
4✔
898
        updateOptions.SetUpsert(true)
4✔
899
        if _, err := c.UpdateOne(
4✔
900
                ctx, query, bson.M{"$set": lim}, updateOptions); err != nil {
4✔
901
                return errors.Wrap(err, "failed to set or update limit")
×
902
        }
×
903

904
        return nil
4✔
905
}
906

907
func (db *DataStoreMongo) DeleteLimit(ctx context.Context, lim string) error {
1✔
908
        if lim == "" {
1✔
909
                return errors.New("empty limit name")
×
910
        }
×
911

912
        c := db.client.Database(DbName).Collection(DbLimitsColl)
1✔
913

1✔
914
        query := ctxstore.WithTenantID(ctx, bson.M{dbFieldName: lim})
1✔
915

1✔
916
        if _, err := c.DeleteOne(ctx, query); err != nil {
1✔
917
                return errors.Wrap(err, "failed to delete limit")
×
918
        }
×
919

920
        return nil
1✔
921
}
922

923
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
5✔
924
        c := db.client.Database(DbName).Collection(DbLimitsColl)
5✔
925

5✔
926
        var lim model.Limit
5✔
927

5✔
928
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{dbFieldName: name})).Decode(&lim)
5✔
929

5✔
930
        if err != nil {
8✔
931
                if err == mongo.ErrNoDocuments {
6✔
932
                        return nil, store.ErrLimitNotFound
3✔
933
                } else {
3✔
934
                        return nil, errors.Wrap(err, "failed to fetch limit")
×
935
                }
×
936
        }
937

938
        return &lim, nil
3✔
939
}
940

941
func (db *DataStoreMongo) GetDevCountByStatus(ctx context.Context, status string) (int, error) {
85✔
942
        var (
85✔
943
                fltr     = bson.D{}
85✔
944
                devsColl = db.client.
85✔
945
                                Database(DbName).
85✔
946
                                Collection(DbDevicesColl)
85✔
947
        )
85✔
948

85✔
949
        if status != "" {
156✔
950
                fltr = bson.D{{Key: "status", Value: status}}
71✔
951
        }
71✔
952
        count, err := devsColl.CountDocuments(ctx, ctxstore.WithTenantID(ctx, fltr))
85✔
953
        if err != nil {
85✔
954
                return 0, err
×
955
        }
×
956
        return int(count), nil
85✔
957
}
958

959
func (db *DataStoreMongo) GetDeviceStatus(ctx context.Context, devId string) (string, error) {
9✔
960
        id := identity.FromContext(ctx)
9✔
961
        tenantId := ""
9✔
962
        if id != nil {
17✔
963
                tenantId = id.Tenant
8✔
964
        }
8✔
965
        return getDeviceStatusDB(db, DbName, tenantId, ctx, devId)
9✔
966
}
967

968
func getDeviceStatus(statuses map[string]int) (string, error) {
16✔
969
        if statuses[model.DevStatusAccepted] > 1 || statuses[model.DevStatusPreauth] > 1 {
18✔
970
                return "", store.ErrDevStatusBroken
2✔
971
        }
2✔
972

973
        if statuses[model.DevStatusAccepted] == 1 {
19✔
974
                return model.DevStatusAccepted, nil
5✔
975
        }
5✔
976

977
        if statuses[model.DevStatusPreauth] == 1 {
11✔
978
                return model.DevStatusPreauth, nil
1✔
979
        }
1✔
980

981
        if statuses[model.DevStatusPending] > 0 {
14✔
982
                return model.DevStatusPending, nil
5✔
983
        }
5✔
984

985
        if statuses[model.DevStatusRejected] > 0 {
10✔
986
                return model.DevStatusRejected, nil
5✔
987
        }
5✔
988

989
        return "", store.ErrDevStatusBroken
×
990
}
991

992
func getDeviceStatusDB(
993
        db *DataStoreMongo,
994
        dbName string,
995
        tenantId string,
996
        ctx context.Context,
997
        devId string,
998
) (string, error) {
19✔
999
        var statuses = map[string]int{}
19✔
1000

19✔
1001
        c := db.client.Database(dbName).Collection(DbAuthSetColl)
19✔
1002

19✔
1003
        filter := model.AuthSet{
19✔
1004
                DeviceId: devId,
19✔
1005
        }
19✔
1006
        if tenantId != "" {
27✔
1007
                filter.TenantID = tenantId
8✔
1008
        }
8✔
1009

1010
        match := bson.D{
19✔
1011
                {Key: "$match", Value: filter},
19✔
1012
        }
19✔
1013
        group := bson.D{
19✔
1014
                {Key: "$group", Value: bson.D{
19✔
1015
                        {Key: "_id", Value: "$status"},
19✔
1016
                        {Key: "count", Value: bson.M{"$sum": 1}}},
19✔
1017
                },
19✔
1018
        }
19✔
1019

19✔
1020
        pipeline := []bson.D{
19✔
1021
                match,
19✔
1022
                group,
19✔
1023
        }
19✔
1024
        var result []struct {
19✔
1025
                Status string `bson:"_id"`
19✔
1026
                Value  int    `bson:"count"`
19✔
1027
        }
19✔
1028
        cursor, err := c.Aggregate(ctx, pipeline)
19✔
1029
        if err != nil {
19✔
1030
                return "", err
×
1031
        }
×
1032
        if err := cursor.All(ctx, &result); err != nil {
19✔
1033
                if err == mongo.ErrNoDocuments {
×
1034
                        return "", store.ErrAuthSetNotFound
×
1035
                }
×
1036
                return "", err
×
1037
        }
1038

1039
        if len(result) == 0 {
23✔
1040
                return "", store.ErrAuthSetNotFound
4✔
1041
        }
4✔
1042

1043
        for _, res := range result {
41✔
1044
                statuses[res.Status] = res.Value
25✔
1045
        }
25✔
1046

1047
        status, err := getDeviceStatus(statuses)
16✔
1048
        if err != nil {
18✔
1049
                return "", err
2✔
1050
        }
2✔
1051

1052
        return status, nil
14✔
1053
}
1054

1055
func (db *DataStoreMongo) ListTenantsIds(
1056
        ctx context.Context,
1057
) ([]string, error) {
4✔
1058
        collDevs := db.client.
4✔
1059
                Database(DbName).
4✔
1060
                Collection(DbDevicesColl)
4✔
1061

4✔
1062
        results, err := collDevs.Distinct(ctx, dbFieldTenantID, bson.D{})
4✔
1063
        if err != nil {
5✔
1064
                return []string{}, nil
1✔
1065
        }
1✔
1066
        if len(results) < 1 {
3✔
1067
                return []string{}, mongo.ErrNoDocuments
×
1068
        }
×
1069
        ids := make([]string, len(results))
3✔
1070
        for i, id := range results {
9✔
1071
                ids[i] = id.(string)
6✔
1072
        }
6✔
1073
        return ids, nil
3✔
1074
}
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