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

mendersoftware / deviceauth / 833840828

pending completion
833840828

Pull #638

gitlab-ci

Peter Grzybowski
chore: review fixes
Pull Request #638: chore: moving to single db

344 of 426 new or added lines in 5 files covered. (80.75%)

36 existing lines in 3 files now uncovered.

4680 of 5608 relevant lines covered (83.45%)

86.46 hits per line

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

88.61
/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
)
58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

179
type DeviceFilter model.DeviceFilter
180

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

208
        return bson.Marshal(doc)
23✔
209
}
210

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

23✔
226
        findOpts := mopts.Find().
23✔
227
                SetSort(bson.D{{Key: "_id", Value: 1}}).
23✔
228
                SetSkip(int64(skip) & MaxInt64)
23✔
229

23✔
230
        if limit > 0 {
46✔
231
                findOpts.SetLimit(int64(limit))
23✔
232
        }
23✔
233

234
        cursor, err := collDevs.Find(ctx, ctxstore.WithTenantID(ctx, fltr), findOpts)
23✔
235
        if err != nil {
23✔
236
                return nil, errors.Wrap(err, "failed to fetch device list")
×
237
        }
×
238
        if err := cursor.All(ctx, &res); err != nil {
23✔
239
                return nil, err
×
240
        }
×
241

242
        return res, nil
23✔
243
}
244

245
func (db *DataStoreMongo) StoreMigrationVersion(
246
        ctx context.Context,
247
        version *migrate.Version,
248
) error {
4✔
249
        if version == nil {
6✔
250
                return errors.New("version cant be nil.")
2✔
251
        }
2✔
252

253
        c := db.client.Database(DbName).
2✔
254
                Collection(migrate.DbMigrationsColl)
2✔
255

2✔
256
        migrationInfo := migrate.MigrationEntry{
2✔
257
                Version:   *version,
2✔
258
                Timestamp: time.Now(),
2✔
259
        }
2✔
260
        _, err := c.InsertOne(ctx, migrationInfo)
2✔
261
        return err
2✔
262
}
263

264
func (db *DataStoreMongo) GetDeviceById(ctx context.Context, id string) (*model.Device, error) {
9✔
265

9✔
266
        c := db.client.Database(DbName).Collection(DbDevicesColl)
9✔
267

9✔
268
        res := model.Device{}
9✔
269

9✔
270
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": id})).Decode(&res)
9✔
271

9✔
272
        if err != nil {
12✔
273
                if err == mongo.ErrNoDocuments {
6✔
274
                        return nil, store.ErrDevNotFound
3✔
275
                } else {
3✔
276
                        return nil, errors.Wrap(err, "failed to fetch device")
×
277
                }
×
278
        }
279

280
        return &res, nil
7✔
281
}
282

283
func (db *DataStoreMongo) GetDeviceByIdentityDataHash(
284
        ctx context.Context,
285
        idataHash []byte,
286
) (*model.Device, error) {
11✔
287
        c := db.client.Database(DbName).Collection(DbDevicesColl)
11✔
288

11✔
289
        filter := ctxstore.WithTenantID(ctx, bson.M{dbFieldIDDataSha: idataHash})
11✔
290
        res := model.Device{}
11✔
291

11✔
292
        err := c.FindOne(ctx, filter).Decode(&res)
11✔
293
        if err != nil {
15✔
294
                if err == mongo.ErrNoDocuments {
8✔
295
                        return nil, store.ErrDevNotFound
4✔
296
                } else {
4✔
297
                        return nil, errors.Wrap(err, "failed to fetch device")
×
298
                }
×
299
        }
300

301
        return &res, nil
7✔
302
}
303

304
func (db *DataStoreMongo) AddDevice(ctx context.Context, d model.Device) error {
693✔
305

693✔
306
        if d.Id == "" {
696✔
307
                uid := oid.NewUUIDv4()
3✔
308
                d.Id = uid.String()
3✔
309
        }
3✔
310

311
        id := identity.FromContext(ctx)
693✔
312
        tenantId := ""
693✔
313
        if id != nil {
1,386✔
314
                tenantId = id.Tenant
693✔
315
        }
693✔
316
        d.TenantID = tenantId
693✔
317

693✔
318
        c := db.client.Database(DbName).Collection(DbDevicesColl)
693✔
319

693✔
320
        if _, err := c.InsertOne(ctx, d); err != nil {
698✔
321
                if strings.Contains(err.Error(), "duplicate key error") {
10✔
322
                        return store.ErrObjectExists
5✔
323
                }
5✔
324
                return errors.Wrap(err, "failed to store device")
×
325
        }
326
        return nil
689✔
327
}
328

329
func (db *DataStoreMongo) UpdateDevice(ctx context.Context,
330
        deviceID string, updev model.DeviceUpdate) error {
9✔
331

9✔
332
        c := db.client.Database(DbName).Collection(DbDevicesColl)
9✔
333

9✔
334
        updev.UpdatedTs = uto.TimePtr(time.Now().UTC())
9✔
335
        update := bson.M{
9✔
336
                "$inc": bson.M{
9✔
337
                        DbKeyDeviceRevision: 1,
9✔
338
                },
9✔
339
                "$set": updev,
9✔
340
        }
9✔
341

9✔
342
        res, err := c.UpdateOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": deviceID}), update)
9✔
343
        if err != nil {
9✔
344
                return errors.Wrap(err, "failed to update device")
×
345
        } else if res.MatchedCount < 1 {
16✔
346
                return store.ErrDevNotFound
7✔
347
        }
7✔
348

349
        return nil
3✔
350
}
351

352
func (db *DataStoreMongo) DeleteDevice(ctx context.Context, id string) error {
9✔
353

9✔
354
        c := db.client.Database(DbName).Collection(DbDevicesColl)
9✔
355

9✔
356
        filter := ctxstore.WithTenantID(ctx, bson.M{"_id": id})
9✔
357
        result, err := c.DeleteOne(ctx, filter)
9✔
358
        if err != nil {
9✔
359
                return errors.Wrap(err, "failed to remove device")
×
360
        } else if result.DeletedCount < 1 {
15✔
361
                return store.ErrDevNotFound
6✔
362
        }
6✔
363

364
        return nil
3✔
365
}
366

367
func (db *DataStoreMongo) AddToken(ctx context.Context, t *jwt.Token) error {
3✔
368
        database := db.client.Database(DbName)
3✔
369
        collTokens := database.Collection(DbTokensColl)
3✔
370

3✔
371
        filter := bson.M{"_id": t.Claims.ID}
3✔
372
        update := bson.M{"$set": t}
3✔
373
        updateOpts := mopts.Update()
3✔
374
        updateOpts.SetUpsert(true)
3✔
375

3✔
376
        if _, err := collTokens.UpdateOne(
3✔
377
                ctx, filter, update, updateOpts,
3✔
378
        ); err != nil {
3✔
379
                return errors.Wrap(err, "failed to store token")
×
380
        }
×
381

382
        return nil
3✔
383
}
384

385
func (db *DataStoreMongo) GetToken(
386
        ctx context.Context,
387
        jti oid.ObjectID,
388
) (*jwt.Token, error) {
9✔
389

9✔
390
        c := db.client.Database(DbName).Collection(DbTokensColl)
9✔
391

9✔
392
        res := jwt.Token{}
9✔
393

9✔
394
        err := c.FindOne(ctx, bson.M{"_id": jti}).Decode(&res)
9✔
395
        if err != nil {
14✔
396
                if err == mongo.ErrNoDocuments {
10✔
397
                        return nil, store.ErrTokenNotFound
5✔
398
                } else {
5✔
399
                        return nil, errors.Wrap(err, "failed to fetch token")
×
400
                }
×
401
        }
402

403
        return &res, nil
5✔
404
}
405

406
func (db *DataStoreMongo) DeleteToken(ctx context.Context, jti oid.ObjectID) error {
9✔
407
        database := db.client.Database(DbName)
9✔
408
        collTokens := database.Collection(DbTokensColl)
9✔
409

9✔
410
        filter := bson.M{"_id": jti}
9✔
411
        result, err := collTokens.DeleteOne(ctx, filter)
9✔
412
        if err != nil {
9✔
413
                return errors.Wrap(err, "failed to remove token")
×
414
        } else if result.DeletedCount < 1 {
13✔
415
                return store.ErrTokenNotFound
4✔
416
        }
4✔
417

418
        return nil
5✔
419
}
420

421
func (db *DataStoreMongo) DeleteTokens(ctx context.Context) error {
7✔
422
        c := db.client.Database(DbName).
7✔
423
                Collection(DbTokensColl)
7✔
424

7✔
425
        id := identity.FromContext(ctx)
7✔
426
        filter := bson.M{}
7✔
427
        // I have some doubts in here: we are dropping all the tokens if
7✔
428
        // tenant claim in the JWT is empty
7✔
429
        if id != nil {
10✔
430
                if id.Tenant != "" {
6✔
431
                        filter = bson.M{dbFieldTenantClaim: id.Tenant}
3✔
432
                }
3✔
433
        }
434
        _, err := c.DeleteMany(ctx, filter)
7✔
435

7✔
436
        return err
7✔
437
}
438

439
func (db *DataStoreMongo) DeleteTokenByDevId(
440
        ctx context.Context,
441
        devID oid.ObjectID,
442
) error {
7✔
443
        c := db.client.Database(DbName).
7✔
444
                Collection(DbTokensColl)
7✔
445
        var filter interface{}
7✔
446
        id := identity.FromContext(ctx)
7✔
447
        if id != nil {
12✔
448
                // in here if mender.tenant is not set we will not match,
5✔
449
                // since there is no way to tell looking at id if we wanted empty Tenant
5✔
450
                // or nil. we could have either change AddToken or remove omitempty.
5✔
451
                if id.Tenant == "" {
5✔
NEW
452
                        filter = bson.M{
×
NEW
453
                                "$or": []bson.M{
×
NEW
454
                                        {"sub": devID},
×
NEW
455
                                        {
×
NEW
456
                                                "$and": []bson.M{
×
NEW
457
                                                        {"sub": devID},
×
NEW
458
                                                        {dbFieldTenantClaim: ""},
×
NEW
459
                                                },
×
NEW
460
                                        },
×
NEW
461
                                },
×
NEW
462
                        }
×
463
                } else {
5✔
464
                        filter = bson.D{
5✔
465
                                {Key: "sub", Value: devID},
5✔
466
                                {Key: dbFieldTenantClaim, Value: id.Tenant},
5✔
467
                        }
5✔
468
                }
5✔
469
        } else {
3✔
470
                filter = bson.M{"sub": devID}
3✔
471
        }
3✔
472
        ci, err := c.DeleteMany(ctx, filter)
7✔
473

7✔
474
        if err != nil {
7✔
475
                return errors.Wrap(err, "failed to remove tokens")
×
476
        }
×
477

478
        if ci.DeletedCount == 0 {
10✔
479
                return store.ErrTokenNotFound
3✔
480
        }
3✔
481

482
        return nil
5✔
483
}
484

485
func (db *DataStoreMongo) Migrate(ctx context.Context, version string) error {
139✔
486
        l := log.FromContext(ctx)
139✔
487

139✔
488
        dbs := []string{DbName}
139✔
489

139✔
490
        if db.multitenant {
144✔
491
                l.Infof("running migrations in multitenant mode")
5✔
492

5✔
493
                tdbs, err := migrate.GetTenantDbs(ctx, db.client, ctxstore.IsTenantDb(DbName))
5✔
494
                if err != nil {
5✔
495
                        return errors.Wrap(err, "failed go retrieve tenant DBs")
×
496
                }
×
497
                dbs = append(dbs, tdbs...)
5✔
498
        } else {
135✔
499
                l.Infof("running migrations in single tenant mode")
135✔
500
        }
135✔
501

502
        if db.automigrate {
274✔
503
                l.Infof("automigrate is ON, will apply migrations")
135✔
504
        } else {
139✔
505
                l.Infof("automigrate is OFF, will check db version compatibility")
4✔
506
        }
4✔
507

508
        for _, d := range dbs {
282✔
509
                // if not in multi tenant, then tenant will be "" and identity
143✔
510
                // will be the same as default
143✔
511
                tenant := ctxstore.TenantFromDbName(d, DbName)
143✔
512

143✔
513
                tenantCtx := identity.WithContext(ctx, &identity.Identity{
143✔
514
                        Tenant: tenant,
143✔
515
                })
143✔
516

143✔
517
                // TODO: we should aim to unify context usage across migrators and migrations
143✔
518
                // migrators use 'string' db name, migrations use DbFromContext
143✔
519
                // both should use one or the other; for now - just redundantly pass both ctx and db name
143✔
520
                err := db.MigrateTenant(tenantCtx, d, version)
143✔
521
                if err != nil {
149✔
522
                        return err
6✔
523
                }
6✔
524
        }
525

526
        return nil
133✔
527
}
528

529
func (db *DataStoreMongo) MigrateTenant(ctx context.Context, database, version string) error {
145✔
530
        l := log.FromContext(ctx)
145✔
531

145✔
532
        l.Infof("migrating %s", database)
145✔
533

145✔
534
        m := migrate.SimpleMigrator{
145✔
535
                Client:      db.client,
145✔
536
                Db:          database,
145✔
537
                Automigrate: db.automigrate,
145✔
538
        }
145✔
539

145✔
540
        migrations := []migrate.Migration{
145✔
541
                &migration_1_1_0{
145✔
542
                        ms:  db,
145✔
543
                        ctx: ctx,
145✔
544
                },
145✔
545
                &migration_1_2_0{
145✔
546
                        ms:  db,
145✔
547
                        ctx: ctx,
145✔
548
                },
145✔
549
                &migration_1_3_0{
145✔
550
                        ms:  db,
145✔
551
                        ctx: ctx,
145✔
552
                },
145✔
553
                &migration_1_4_0{
145✔
554
                        ms:  db,
145✔
555
                        ctx: ctx,
145✔
556
                },
145✔
557
                &migration_1_5_0{
145✔
558
                        ms:  db,
145✔
559
                        ctx: ctx,
145✔
560
                },
145✔
561
                &migration_1_6_0{
145✔
562
                        ms:  db,
145✔
563
                        ctx: ctx,
145✔
564
                },
145✔
565
                &migration_1_7_0{
145✔
566
                        ms:  db,
145✔
567
                        ctx: ctx,
145✔
568
                },
145✔
569
                &migration_1_8_0{
145✔
570
                        ds:  db,
145✔
571
                        ctx: ctx,
145✔
572
                },
145✔
573
                &migration_1_9_0{
145✔
574
                        ds:  db,
145✔
575
                        ctx: ctx,
145✔
576
                },
145✔
577
                &migration_1_10_0{
145✔
578
                        ds:  db,
145✔
579
                        ctx: ctx,
145✔
580
                },
145✔
581
                &migration_1_11_0{
145✔
582
                        ds:  db,
145✔
583
                        ctx: ctx,
145✔
584
                },
145✔
585
                &migration_2_0_0{
145✔
586
                        ds:  db,
145✔
587
                        ctx: ctx,
145✔
588
                },
145✔
589
        }
145✔
590

145✔
591
        ver, err := migrate.NewVersion(version)
145✔
592
        if err != nil {
147✔
593
                return errors.Wrap(err, "failed to parse service version")
2✔
594
        }
2✔
595

596
        err = m.Apply(ctx, *ver, migrations)
143✔
597
        if err != nil {
147✔
598
                return errors.Wrap(err, "failed to apply migrations")
4✔
599
        }
4✔
600

601
        return nil
139✔
602
}
603

604
func (db *DataStoreMongo) AddAuthSet(ctx context.Context, set model.AuthSet) error {
1,203✔
605
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
1,203✔
606

1,203✔
607
        if set.Id == "" {
2,405✔
608
                uid := oid.NewUUIDv4()
1,202✔
609
                set.Id = uid.String()
1,202✔
610
        }
1,202✔
611
        id := identity.FromContext(ctx)
1,203✔
612
        tenantId := ""
1,203✔
613
        if id != nil {
2,406✔
614
                tenantId = id.Tenant
1,203✔
615
        }
1,203✔
616
        set.TenantID = tenantId
1,203✔
617

1,203✔
618
        if _, err := c.InsertOne(ctx, set); err != nil {
1,204✔
619
                if strings.Contains(err.Error(), "duplicate key error") {
2✔
620
                        return store.ErrObjectExists
1✔
621
                }
1✔
622
                return errors.Wrap(err, "failed to store device")
×
623
        }
624

625
        return nil
1,203✔
626
}
627

628
func (db *DataStoreMongo) GetAuthSetByIdDataHashKey(
629
        ctx context.Context,
630
        idDataHash []byte,
631
        key string,
632
) (*model.AuthSet, error) {
11✔
633
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
11✔
634

11✔
635
        id := identity.FromContext(ctx)
11✔
636
        tenantId := ""
11✔
637
        if id != nil {
20✔
638
                tenantId = id.Tenant
9✔
639
        }
9✔
640

641
        filter := model.AuthSet{
11✔
642
                IdDataSha256: idDataHash,
11✔
643
                PubKey:       key,
11✔
644
                TenantID:     tenantId,
11✔
645
        }
11✔
646
        res := model.AuthSet{}
11✔
647

11✔
648
        err := c.FindOne(ctx, filter).Decode(&res)
11✔
649
        if err != nil {
16✔
650
                if err == mongo.ErrNoDocuments {
10✔
651
                        return nil, store.ErrAuthSetNotFound
5✔
652
                } else {
5✔
653
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
654
                }
×
655
        }
656

657
        return &res, nil
7✔
658
}
659

660
func (db *DataStoreMongo) GetAuthSetById(
661
        ctx context.Context,
662
        auth_id string,
663
) (*model.AuthSet, error) {
5✔
664
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
5✔
665

5✔
666
        res := model.AuthSet{}
5✔
667
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": auth_id})).Decode(&res)
5✔
668
        if err != nil {
8✔
669
                if err == mongo.ErrNoDocuments {
6✔
670
                        return nil, store.ErrAuthSetNotFound
3✔
671
                } else {
3✔
672
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
673
                }
×
674
        }
675

676
        return &res, nil
3✔
677
}
678

679
func (db *DataStoreMongo) GetAuthSetsForDevice(
680
        ctx context.Context,
681
        devid string,
682
) ([]model.AuthSet, error) {
11✔
683
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
11✔
684

11✔
685
        res := []model.AuthSet{}
11✔
686

11✔
687
        id := identity.FromContext(ctx)
11✔
688
        tenantId := ""
11✔
689
        if id != nil {
22✔
690
                tenantId = id.Tenant
11✔
691
        }
11✔
692
        cursor, err := c.Find(ctx, model.AuthSet{DeviceId: devid, TenantID: tenantId})
11✔
693
        if err != nil {
11✔
694
                return nil, err
×
695
        }
×
696
        if err = cursor.All(ctx, &res); err != nil {
11✔
697
                if err == mongo.ErrNoDocuments {
×
698
                        return nil, store.ErrAuthSetNotFound
×
699
                }
×
700
                return nil, errors.Wrap(err, "failed to fetch authentication sets")
×
701
        }
702

703
        return res, nil
11✔
704
}
705

706
func (db *DataStoreMongo) UpdateAuthSet(
707
        ctx context.Context,
708
        filter interface{},
709
        mod model.AuthSetUpdate,
710
) error {
13✔
711
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
13✔
712

13✔
713
        update := bson.M{"$set": mod}
13✔
714

13✔
715
        if res, err := c.UpdateMany(ctx, ctxstore.WithTenantID(ctx, filter), update); err != nil {
13✔
716
                return errors.Wrap(err, "failed to update auth set")
×
717
        } else if res.MatchedCount == 0 {
18✔
718
                return store.ErrAuthSetNotFound
5✔
719
        }
5✔
720

721
        return nil
9✔
722
}
723

724
func (db *DataStoreMongo) UpdateAuthSetById(
725
        ctx context.Context,
726
        id string,
727
        mod model.AuthSetUpdate,
728
) error {
11✔
729
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
11✔
730
        res, err := c.UpdateOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": id}), bson.M{"$set": mod})
11✔
731
        if err != nil {
11✔
732
                return errors.Wrap(err, "failed to update auth set")
×
733
        }
×
734
        if res.MatchedCount == 0 {
13✔
735
                return store.ErrAuthSetNotFound
2✔
736
        }
2✔
737

738
        return nil
9✔
739
}
740

741
func (db *DataStoreMongo) DeleteAuthSetsForDevice(ctx context.Context, devid string) error {
7✔
742
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
7✔
743
        id := identity.FromContext(ctx)
7✔
744
        tenantId := ""
7✔
745
        if id != nil {
13✔
746
                tenantId = id.Tenant
6✔
747
        }
6✔
748

749
        ci, err := c.DeleteMany(
7✔
750
                ctx,
7✔
751
                model.AuthSet{
7✔
752
                        DeviceId: devid,
7✔
753
                        TenantID: tenantId,
7✔
754
                },
7✔
755
        )
7✔
756

7✔
757
        if err != nil {
7✔
758
                return errors.Wrap(err, "failed to remove authentication sets for device")
×
759
        }
×
760

761
        if ci.DeletedCount == 0 {
9✔
762
                return store.ErrAuthSetNotFound
2✔
763
        }
2✔
764

765
        return nil
5✔
766
}
767

768
func (db *DataStoreMongo) DeleteAuthSetForDevice(
769
        ctx context.Context,
770
        devId string,
771
        authId string,
772
) error {
7✔
773
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
7✔
774

7✔
775
        id := identity.FromContext(ctx)
7✔
776
        tenantId := ""
7✔
777
        if id != nil {
12✔
778
                tenantId = id.Tenant
5✔
779
        }
5✔
780
        filter := model.AuthSet{Id: authId, DeviceId: devId, TenantID: tenantId}
7✔
781
        result, err := c.DeleteOne(ctx, filter)
7✔
782
        if err != nil {
7✔
783
                return errors.Wrap(err, "failed to remove authentication set for device")
×
784
        } else if result.DeletedCount < 1 {
11✔
785
                return store.ErrAuthSetNotFound
4✔
786
        }
4✔
787

788
        return nil
3✔
789
}
790

791
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
5✔
792
        db.multitenant = true
5✔
793
        return db
5✔
794
}
5✔
795

796
func (db *DataStoreMongo) WithAutomigrate() store.DataStore {
135✔
797
        return &DataStoreMongo{
135✔
798
                client:      db.client,
135✔
799
                automigrate: true,
135✔
800
        }
135✔
801
}
135✔
802

803
func (db *DataStoreMongo) PutLimit(ctx context.Context, lim model.Limit) error {
9✔
804
        if lim.Name == "" {
11✔
805
                return errors.New("empty limit name")
2✔
806
        }
2✔
807

808
        c := db.client.Database(DbName).Collection(DbLimitsColl)
7✔
809
        id := identity.FromContext(ctx)
7✔
810
        tenantId := ""
7✔
811
        if id != nil {
14✔
812
                tenantId = id.Tenant
7✔
813
        }
7✔
814

815
        lim.TenantID = tenantId
7✔
816
        query := ctxstore.WithTenantID(ctx, bson.M{dbFieldName: lim.Name})
7✔
817

7✔
818
        updateOptions := mopts.Update()
7✔
819
        updateOptions.SetUpsert(true)
7✔
820
        if _, err := c.UpdateOne(
7✔
821
                ctx, query, bson.M{"$set": lim}, updateOptions); err != nil {
7✔
822
                return errors.Wrap(err, "failed to set or update limit")
×
823
        }
×
824

825
        return nil
7✔
826
}
827

828
func (db *DataStoreMongo) DeleteLimit(ctx context.Context, lim string) error {
2✔
829
        if lim == "" {
2✔
830
                return errors.New("empty limit name")
×
831
        }
×
832

833
        c := db.client.Database(DbName).Collection(DbLimitsColl)
2✔
834

2✔
835
        query := ctxstore.WithTenantID(ctx, bson.M{dbFieldName: lim})
2✔
836

2✔
837
        if _, err := c.DeleteOne(ctx, query); err != nil {
2✔
838
                return errors.Wrap(err, "failed to delete limit")
×
839
        }
×
840

841
        return nil
2✔
842
}
843

844
func (db *DataStoreMongo) GetLimit(ctx context.Context, name string) (*model.Limit, error) {
9✔
845
        c := db.client.Database(DbName).Collection(DbLimitsColl)
9✔
846

9✔
847
        var lim model.Limit
9✔
848

9✔
849
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{dbFieldName: name})).Decode(&lim)
9✔
850

9✔
851
        if err != nil {
14✔
852
                if err == mongo.ErrNoDocuments {
10✔
853
                        return nil, store.ErrLimitNotFound
5✔
854
                } else {
5✔
855
                        return nil, errors.Wrap(err, "failed to fetch limit")
×
856
                }
×
857
        }
858

859
        return &lim, nil
5✔
860
}
861

862
func (db *DataStoreMongo) GetDevCountByStatus(ctx context.Context, status string) (int, error) {
169✔
863
        var (
169✔
864
                fltr     = bson.D{}
169✔
865
                devsColl = db.client.
169✔
866
                                Database(DbName).
169✔
867
                                Collection(DbDevicesColl)
169✔
868
        )
169✔
869

169✔
870
        if status != "" {
310✔
871
                fltr = bson.D{{Key: "status", Value: status}}
141✔
872
        }
141✔
873
        count, err := devsColl.CountDocuments(ctx, ctxstore.WithTenantID(ctx, fltr))
169✔
874
        if err != nil {
169✔
875
                return 0, err
×
876
        }
×
877
        return int(count), nil
169✔
878
}
879

880
func (db *DataStoreMongo) GetDeviceStatus(ctx context.Context, devId string) (string, error) {
17✔
881
        id := identity.FromContext(ctx)
17✔
882
        tenantId := ""
17✔
883
        if id != nil {
34✔
884
                tenantId = id.Tenant
17✔
885
        }
17✔
886
        return getDeviceStatusDB(db, DbName, tenantId, ctx, devId)
17✔
887
}
888

889
func getDeviceStatus(statuses map[string]int) (string, error) {
31✔
890
        if statuses[model.DevStatusAccepted] > 1 || statuses[model.DevStatusPreauth] > 1 {
35✔
891
                return "", store.ErrDevStatusBroken
4✔
892
        }
4✔
893

894
        if statuses[model.DevStatusAccepted] == 1 {
36✔
895
                return model.DevStatusAccepted, nil
9✔
896
        }
9✔
897

898
        if statuses[model.DevStatusPreauth] == 1 {
21✔
899
                return model.DevStatusPreauth, nil
2✔
900
        }
2✔
901

902
        if statuses[model.DevStatusPending] > 0 {
26✔
903
                return model.DevStatusPending, nil
9✔
904
        }
9✔
905

906
        if statuses[model.DevStatusRejected] > 0 {
18✔
907
                return model.DevStatusRejected, nil
9✔
908
        }
9✔
909

NEW
910
        return "", store.ErrDevStatusBroken
×
911
}
912

913
func getDeviceStatusDB(
914
        db *DataStoreMongo,
915
        dbName string,
916
        tenantId string,
917
        ctx context.Context,
918
        devId string,
919
) (string, error) {
37✔
920
        var statuses = map[string]int{}
37✔
921

37✔
922
        c := db.client.Database(dbName).Collection(DbAuthSetColl)
37✔
923

37✔
924
        filter := model.AuthSet{
37✔
925
                DeviceId: devId,
37✔
926
        }
37✔
927
        if tenantId != "" {
54✔
928
                filter.TenantID = tenantId
17✔
929
        }
17✔
930

931
        match := bson.D{
37✔
932
                {Key: "$match", Value: filter},
37✔
933
        }
37✔
934
        group := bson.D{
37✔
935
                {Key: "$group", Value: bson.D{
37✔
936
                        {Key: "_id", Value: "$status"},
37✔
937
                        {Key: "count", Value: bson.M{"$sum": 1}}},
37✔
938
                },
37✔
939
        }
37✔
940

37✔
941
        pipeline := []bson.D{
37✔
942
                match,
37✔
943
                group,
37✔
944
        }
37✔
945
        var result []struct {
37✔
946
                Status string `bson:"_id"`
37✔
947
                Value  int    `bson:"count"`
37✔
948
        }
37✔
949
        cursor, err := c.Aggregate(ctx, pipeline)
37✔
950
        if err != nil {
37✔
951
                return "", err
×
952
        }
×
953
        if err := cursor.All(ctx, &result); err != nil {
37✔
954
                if err == mongo.ErrNoDocuments {
×
955
                        return "", store.ErrAuthSetNotFound
×
956
                }
×
957
                return "", err
×
958
        }
959

960
        if len(result) == 0 {
44✔
961
                return "", store.ErrAuthSetNotFound
7✔
962
        }
7✔
963

964
        for _, res := range result {
80✔
965
                statuses[res.Status] = res.Value
49✔
966
        }
49✔
967

968
        status, err := getDeviceStatus(statuses)
31✔
969
        if err != nil {
35✔
970
                return "", err
4✔
971
        }
4✔
972

973
        return status, nil
27✔
974
}
975

976
func (db *DataStoreMongo) ListTenantsIds(
977
        ctx context.Context,
978
) ([]string, error) {
9✔
979
        collDevs := db.client.
9✔
980
                Database(DbName).
9✔
981
                Collection(DbDevicesColl)
9✔
982

9✔
983
        results, err := collDevs.Distinct(ctx, dbFieldTenantID, bson.D{})
9✔
984
        if err != nil {
11✔
985
                return []string{}, nil
2✔
986
        }
2✔
987
        if len(results) < 1 {
7✔
NEW
988
                return []string{}, mongo.ErrNoDocuments
×
UNCOV
989
        }
×
990
        ids := make([]string, len(results))
7✔
991
        for i, id := range results {
20✔
992
                ids[i] = id.(string)
13✔
993
        }
13✔
994
        return ids, nil
7✔
995
}
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