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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

86.69
/backend/services/deviceauth/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/pkg/errors"
24
        "go.mongodb.org/mongo-driver/bson"
25
        "go.mongodb.org/mongo-driver/mongo"
26
        mopts "go.mongodb.org/mongo-driver/mongo/options"
27

28
        "github.com/mendersoftware/mender-server/pkg/identity"
29
        "github.com/mendersoftware/mender-server/pkg/log"
30
        "github.com/mendersoftware/mender-server/pkg/mongo/migrate"
31
        "github.com/mendersoftware/mender-server/pkg/mongo/oid"
32
        ctxstore "github.com/mendersoftware/mender-server/pkg/store/v2"
33

34
        "github.com/mendersoftware/mender-server/services/deviceauth/jwt"
35
        "github.com/mendersoftware/mender-server/services/deviceauth/model"
36
        "github.com/mendersoftware/mender-server/services/deviceauth/store"
37
        uto "github.com/mendersoftware/mender-server/services/deviceauth/utils/to"
38
)
39

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

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

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

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

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

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

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

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

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

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

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

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

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

126
        return NewDataStoreMongoWithClient(client), nil
2✔
127
}
128

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

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

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

181
type DeviceFilter model.DeviceFilter
182

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

210
        return bson.Marshal(doc)
2✔
211
}
212

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

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

2✔
235
        if limit > 0 {
4✔
236
                findOpts.SetLimit(int64(limit))
2✔
237
        }
2✔
238

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

247
        return res, nil
2✔
248
}
249

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

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

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

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

1✔
271
        c := db.client.Database(DbName).Collection(DbDevicesColl)
1✔
272

1✔
273
        res := model.Device{}
1✔
274

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

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

285
        return &res, nil
1✔
286
}
287

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

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

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

306
        return &res, nil
1✔
307
}
308

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

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

316
        id := identity.FromContext(ctx)
1✔
317
        tenantId := ""
1✔
318
        if id != nil {
2✔
319
                tenantId = id.Tenant
1✔
320
        }
1✔
321
        d.TenantID = tenantId
1✔
322

1✔
323
        c := db.client.Database(DbName).Collection(DbDevicesColl)
1✔
324

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

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

1✔
337
        c := db.client.Database(DbName).Collection(DbDevicesColl)
1✔
338

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

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

354
        return nil
1✔
355
}
356

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

1✔
359
        c := db.client.Database(DbName).Collection(DbDevicesColl)
1✔
360

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

369
        return nil
1✔
370
}
371

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

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

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

387
        return nil
1✔
388
}
389

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

1✔
395
        c := db.client.Database(DbName).Collection(DbTokensColl)
1✔
396

1✔
397
        res := jwt.Token{}
1✔
398

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

408
        return &res, nil
1✔
409
}
410

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

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

423
        return nil
1✔
424
}
425

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

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

1✔
437
        return err
1✔
438
}
439

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

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

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

461
        return nil
1✔
462
}
463

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

3✔
467
        dbs := []string{DbName}
3✔
468

3✔
469
        if db.multitenant {
4✔
470
                l.Infof("running migrations in multitenant mode")
1✔
471

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

481
        if db.automigrate {
6✔
482
                l.Infof("automigrate is ON, will apply migrations")
3✔
483
        } else {
4✔
484
                l.Infof("automigrate is OFF, will check db version compatibility")
1✔
485
        }
1✔
486

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

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

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

505
        return nil
3✔
506
}
507

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

3✔
511
        l.Infof("migrating %s", database)
3✔
512

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

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

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

575
        err = m.Apply(ctx, *ver, migrations)
3✔
576
        if err != nil {
4✔
577
                return errors.Wrap(err, "failed to apply migrations")
1✔
578
        }
1✔
579

580
        return nil
3✔
581
}
582

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

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

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

611
        return nil
1✔
612
}
613

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

1✔
619
        fillAuthSet(ctx, authSet)
1✔
620

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

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

643
        return nil
1✔
644
}
645

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

1✔
653
        id := identity.FromContext(ctx)
1✔
654
        tenantId := ""
1✔
655
        if id != nil {
2✔
656
                tenantId = id.Tenant
1✔
657
        }
1✔
658

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

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

675
        return &res, nil
1✔
676
}
677

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

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

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

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

709
        return &res, nil
1✔
710
}
711

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

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

728
        return &res, nil
1✔
729
}
730

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

1✔
737
        res := []model.AuthSet{}
1✔
738

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

755
        return res, nil
1✔
756
}
757

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

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

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

UNCOV
785
        return nil
×
786
}
787

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

1✔
795
        update := bson.M{"$set": mod}
1✔
796

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

803
        return nil
1✔
804
}
805

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

820
        return nil
1✔
821
}
822

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

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

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

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

847
        return nil
1✔
848
}
849

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

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

870
        return nil
1✔
871
}
872

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

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

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

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

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

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

905
        return nil
1✔
906
}
907

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

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

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

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

921
        return nil
1✔
922
}
923

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

1✔
927
        var lim model.Limit
1✔
928

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

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

939
        return &lim, nil
1✔
940
}
941

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

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

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

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

974
        if statuses[model.DevStatusAccepted] == 1 {
2✔
975
                return model.DevStatusAccepted, nil
1✔
976
        }
1✔
977

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

982
        if statuses[model.DevStatusPending] > 0 {
2✔
983
                return model.DevStatusPending, nil
1✔
984
        }
1✔
985

986
        if statuses[model.DevStatusRejected] > 0 {
2✔
987
                return model.DevStatusRejected, nil
1✔
988
        }
1✔
989

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

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

1✔
1002
        c := db.client.Database(dbName).Collection(DbAuthSetColl)
1✔
1003

1✔
1004
        filter := model.AuthSet{
1✔
1005
                DeviceId: devId,
1✔
1006
        }
1✔
1007
        if tenantId != "" {
2✔
1008
                filter.TenantID = tenantId
1✔
1009
        }
1✔
1010

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

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

1040
        if len(result) == 0 {
2✔
1041
                return "", store.ErrAuthSetNotFound
1✔
1042
        }
1✔
1043

1044
        for _, res := range result {
2✔
1045
                statuses[res.Status] = res.Value
1✔
1046
        }
1✔
1047

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

1053
        return status, nil
1✔
1054
}
1055

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

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