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

mendersoftware / deviceauth / 850112129

pending completion
850112129

Pull #642

gitlab-ci

Peter Grzybowski
chore: migrate dbName upfront in case of migrating a tenant.
Pull Request #642: Single db move fixes

332 of 405 new or added lines in 5 files covered. (81.98%)

89 existing lines in 3 files now uncovered.

4672 of 5591 relevant lines covered (83.56%)

79.43 hits per line

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

89.78
/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{dbFieldTenantClaim: nil}
7✔
427
        if id != nil && id.Tenant != "" {
10✔
428
                filter[dbFieldTenantClaim] = id.Tenant
3✔
429
        }
3✔
430
        _, err := c.DeleteMany(ctx, filter)
7✔
431

7✔
432
        return err
7✔
433
}
434

435
func (db *DataStoreMongo) DeleteTokenByDevId(
436
        ctx context.Context,
437
        devID oid.ObjectID,
438
) error {
7✔
439
        c := db.client.Database(DbName).
7✔
440
                Collection(DbTokensColl)
7✔
441
        filter := bson.M{"sub": devID, dbFieldTenantClaim: nil}
7✔
442
        id := identity.FromContext(ctx)
7✔
443
        if id != nil && id.Tenant != "" {
12✔
444
                filter[dbFieldTenantClaim] = id.Tenant
5✔
445
        }
5✔
446
        ci, err := c.DeleteMany(ctx, filter)
7✔
447

7✔
448
        if err != nil {
7✔
449
                return errors.Wrap(err, "failed to remove tokens")
×
450
        }
×
451

452
        if ci.DeletedCount == 0 {
10✔
453
                return store.ErrTokenNotFound
3✔
454
        }
3✔
455

456
        return nil
5✔
457
}
458

459
func (db *DataStoreMongo) Migrate(ctx context.Context, version string) error {
139✔
460
        l := log.FromContext(ctx)
139✔
461

139✔
462
        dbs := []string{DbName}
139✔
463

139✔
464
        if db.multitenant {
144✔
465
                l.Infof("running migrations in multitenant mode")
5✔
466

5✔
467
                tdbs, err := migrate.GetTenantDbs(ctx, db.client, ctxstore.IsTenantDb(DbName))
5✔
468
                if err != nil {
5✔
469
                        return errors.Wrap(err, "failed go retrieve tenant DBs")
×
470
                }
×
471
                dbs = append(dbs, tdbs...)
5✔
472
        } else {
135✔
473
                l.Infof("running migrations in single tenant mode")
135✔
474
        }
135✔
475

476
        if db.automigrate {
274✔
477
                l.Infof("automigrate is ON, will apply migrations")
135✔
478
        } else {
139✔
479
                l.Infof("automigrate is OFF, will check db version compatibility")
4✔
480
        }
4✔
481

482
        for _, d := range dbs {
282✔
483
                // if not in multi tenant, then tenant will be "" and identity
143✔
484
                // will be the same as default
143✔
485
                tenant := ctxstore.TenantFromDbName(d, DbName)
143✔
486

143✔
487
                tenantCtx := identity.WithContext(ctx, &identity.Identity{
143✔
488
                        Tenant: tenant,
143✔
489
                })
143✔
490

143✔
491
                // TODO: we should aim to unify context usage across migrators and migrations
143✔
492
                // migrators use 'string' db name, migrations use DbFromContext
143✔
493
                // both should use one or the other; for now - just redundantly pass both ctx and db name
143✔
494
                err := db.MigrateTenant(tenantCtx, d, version)
143✔
495
                if err != nil {
149✔
496
                        return err
6✔
497
                }
6✔
498
        }
499

500
        return nil
133✔
501
}
502

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

145✔
506
        l.Infof("migrating %s", database)
145✔
507

145✔
508
        m := migrate.SimpleMigrator{
145✔
509
                Client:      db.client,
145✔
510
                Db:          database,
145✔
511
                Automigrate: db.automigrate,
145✔
512
        }
145✔
513

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

145✔
565
        ver, err := migrate.NewVersion(version)
145✔
566
        if err != nil {
147✔
567
                return errors.Wrap(err, "failed to parse service version")
2✔
568
        }
2✔
569

570
        err = m.Apply(ctx, *ver, migrations)
143✔
571
        if err != nil {
147✔
572
                return errors.Wrap(err, "failed to apply migrations")
4✔
573
        }
4✔
574

575
        return nil
139✔
576
}
577

578
func (db *DataStoreMongo) AddAuthSet(ctx context.Context, set model.AuthSet) error {
1,211✔
579
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
1,211✔
580

1,211✔
581
        if set.Id == "" {
2,421✔
582
                uid := oid.NewUUIDv4()
1,210✔
583
                set.Id = uid.String()
1,210✔
584
        }
1,210✔
585
        id := identity.FromContext(ctx)
1,211✔
586
        tenantId := ""
1,211✔
587
        if id != nil {
2,422✔
588
                tenantId = id.Tenant
1,211✔
589
        }
1,211✔
590
        set.TenantID = tenantId
1,211✔
591

1,211✔
592
        if _, err := c.InsertOne(ctx, set); err != nil {
1,212✔
593
                if strings.Contains(err.Error(), "duplicate key error") {
2✔
594
                        return store.ErrObjectExists
1✔
595
                }
1✔
596
                return errors.Wrap(err, "failed to store device")
×
597
        }
598

599
        return nil
1,211✔
600
}
601

602
func (db *DataStoreMongo) GetAuthSetByIdDataHashKey(
603
        ctx context.Context,
604
        idDataHash []byte,
605
        key string,
606
) (*model.AuthSet, error) {
11✔
607
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
11✔
608

11✔
609
        id := identity.FromContext(ctx)
11✔
610
        tenantId := ""
11✔
611
        if id != nil {
20✔
612
                tenantId = id.Tenant
9✔
613
        }
9✔
614

615
        filter := model.AuthSet{
11✔
616
                IdDataSha256: idDataHash,
11✔
617
                PubKey:       key,
11✔
618
                TenantID:     tenantId,
11✔
619
        }
11✔
620
        res := model.AuthSet{}
11✔
621

11✔
622
        err := c.FindOne(ctx, filter).Decode(&res)
11✔
623
        if err != nil {
16✔
624
                if err == mongo.ErrNoDocuments {
10✔
625
                        return nil, store.ErrAuthSetNotFound
5✔
626
                } else {
5✔
627
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
628
                }
×
629
        }
630

631
        return &res, nil
7✔
632
}
633

634
func (db *DataStoreMongo) GetAuthSetById(
635
        ctx context.Context,
636
        auth_id string,
637
) (*model.AuthSet, error) {
5✔
638
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
5✔
639

5✔
640
        res := model.AuthSet{}
5✔
641
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": auth_id})).Decode(&res)
5✔
642
        if err != nil {
8✔
643
                if err == mongo.ErrNoDocuments {
6✔
644
                        return nil, store.ErrAuthSetNotFound
3✔
645
                } else {
3✔
646
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
647
                }
×
648
        }
649

650
        return &res, nil
3✔
651
}
652

653
func (db *DataStoreMongo) GetAuthSetsForDevice(
654
        ctx context.Context,
655
        devid string,
656
) ([]model.AuthSet, error) {
11✔
657
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
11✔
658

11✔
659
        res := []model.AuthSet{}
11✔
660

11✔
661
        id := identity.FromContext(ctx)
11✔
662
        tenantId := ""
11✔
663
        if id != nil {
22✔
664
                tenantId = id.Tenant
11✔
665
        }
11✔
666
        cursor, err := c.Find(ctx, model.AuthSet{DeviceId: devid, TenantID: tenantId})
11✔
667
        if err != nil {
11✔
668
                return nil, err
×
669
        }
×
670
        if err = cursor.All(ctx, &res); err != nil {
11✔
671
                if err == mongo.ErrNoDocuments {
×
672
                        return nil, store.ErrAuthSetNotFound
×
673
                }
×
674
                return nil, errors.Wrap(err, "failed to fetch authentication sets")
×
675
        }
676

677
        return res, nil
11✔
678
}
679

680
func (db *DataStoreMongo) UpdateAuthSet(
681
        ctx context.Context,
682
        filter interface{},
683
        mod model.AuthSetUpdate,
684
) error {
13✔
685
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
13✔
686

13✔
687
        update := bson.M{"$set": mod}
13✔
688

13✔
689
        if res, err := c.UpdateMany(ctx, ctxstore.WithTenantID(ctx, filter), update); err != nil {
13✔
690
                return errors.Wrap(err, "failed to update auth set")
×
691
        } else if res.MatchedCount == 0 {
18✔
692
                return store.ErrAuthSetNotFound
5✔
693
        }
5✔
694

695
        return nil
9✔
696
}
697

698
func (db *DataStoreMongo) UpdateAuthSetById(
699
        ctx context.Context,
700
        id string,
701
        mod model.AuthSetUpdate,
702
) error {
11✔
703
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
11✔
704
        res, err := c.UpdateOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": id}), bson.M{"$set": mod})
11✔
705
        if err != nil {
11✔
706
                return errors.Wrap(err, "failed to update auth set")
×
707
        }
×
708
        if res.MatchedCount == 0 {
13✔
709
                return store.ErrAuthSetNotFound
2✔
710
        }
2✔
711

712
        return nil
9✔
713
}
714

715
func (db *DataStoreMongo) DeleteAuthSetsForDevice(ctx context.Context, devid string) error {
7✔
716
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
7✔
717
        id := identity.FromContext(ctx)
7✔
718
        tenantId := ""
7✔
719
        if id != nil {
13✔
720
                tenantId = id.Tenant
6✔
721
        }
6✔
722

723
        ci, err := c.DeleteMany(
7✔
724
                ctx,
7✔
725
                model.AuthSet{
7✔
726
                        DeviceId: devid,
7✔
727
                        TenantID: tenantId,
7✔
728
                },
7✔
729
        )
7✔
730

7✔
731
        if err != nil {
7✔
732
                return errors.Wrap(err, "failed to remove authentication sets for device")
×
733
        }
×
734

735
        if ci.DeletedCount == 0 {
9✔
736
                return store.ErrAuthSetNotFound
2✔
737
        }
2✔
738

739
        return nil
5✔
740
}
741

742
func (db *DataStoreMongo) DeleteAuthSetForDevice(
743
        ctx context.Context,
744
        devId string,
745
        authId string,
746
) error {
7✔
747
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
7✔
748

7✔
749
        id := identity.FromContext(ctx)
7✔
750
        tenantId := ""
7✔
751
        if id != nil {
12✔
752
                tenantId = id.Tenant
5✔
753
        }
5✔
754
        filter := model.AuthSet{Id: authId, DeviceId: devId, TenantID: tenantId}
7✔
755
        result, err := c.DeleteOne(ctx, filter)
7✔
756
        if err != nil {
7✔
757
                return errors.Wrap(err, "failed to remove authentication set for device")
×
758
        } else if result.DeletedCount < 1 {
11✔
759
                return store.ErrAuthSetNotFound
4✔
760
        }
4✔
761

762
        return nil
3✔
763
}
764

765
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
5✔
766
        db.multitenant = true
5✔
767
        return db
5✔
768
}
5✔
769

770
func (db *DataStoreMongo) WithAutomigrate() store.DataStore {
135✔
771
        return &DataStoreMongo{
135✔
772
                client:      db.client,
135✔
773
                automigrate: true,
135✔
774
        }
135✔
775
}
135✔
776

777
func (db *DataStoreMongo) PutLimit(ctx context.Context, lim model.Limit) error {
9✔
778
        if lim.Name == "" {
11✔
779
                return errors.New("empty limit name")
2✔
780
        }
2✔
781

782
        c := db.client.Database(DbName).Collection(DbLimitsColl)
7✔
783
        id := identity.FromContext(ctx)
7✔
784
        tenantId := ""
7✔
785
        if id != nil {
14✔
786
                tenantId = id.Tenant
7✔
787
        }
7✔
788

789
        lim.TenantID = tenantId
7✔
790
        query := ctxstore.WithTenantID(ctx, bson.M{dbFieldName: lim.Name})
7✔
791

7✔
792
        updateOptions := mopts.Update()
7✔
793
        updateOptions.SetUpsert(true)
7✔
794
        if _, err := c.UpdateOne(
7✔
795
                ctx, query, bson.M{"$set": lim}, updateOptions); err != nil {
7✔
796
                return errors.Wrap(err, "failed to set or update limit")
×
797
        }
×
798

799
        return nil
7✔
800
}
801

802
func (db *DataStoreMongo) DeleteLimit(ctx context.Context, lim string) error {
2✔
803
        if lim == "" {
2✔
804
                return errors.New("empty limit name")
×
805
        }
×
806

807
        c := db.client.Database(DbName).Collection(DbLimitsColl)
2✔
808

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

2✔
811
        if _, err := c.DeleteOne(ctx, query); err != nil {
2✔
812
                return errors.Wrap(err, "failed to delete limit")
×
813
        }
×
814

815
        return nil
2✔
816
}
817

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

9✔
821
        var lim model.Limit
9✔
822

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

9✔
825
        if err != nil {
14✔
826
                if err == mongo.ErrNoDocuments {
10✔
827
                        return nil, store.ErrLimitNotFound
5✔
828
                } else {
5✔
829
                        return nil, errors.Wrap(err, "failed to fetch limit")
×
830
                }
×
831
        }
832

833
        return &lim, nil
5✔
834
}
835

836
func (db *DataStoreMongo) GetDevCountByStatus(ctx context.Context, status string) (int, error) {
169✔
837
        var (
169✔
838
                fltr     = bson.D{}
169✔
839
                devsColl = db.client.
169✔
840
                                Database(DbName).
169✔
841
                                Collection(DbDevicesColl)
169✔
842
        )
169✔
843

169✔
844
        if status != "" {
310✔
845
                fltr = bson.D{{Key: "status", Value: status}}
141✔
846
        }
141✔
847
        count, err := devsColl.CountDocuments(ctx, ctxstore.WithTenantID(ctx, fltr))
169✔
848
        if err != nil {
169✔
849
                return 0, err
×
850
        }
×
851
        return int(count), nil
169✔
852
}
853

854
func (db *DataStoreMongo) GetDeviceStatus(ctx context.Context, devId string) (string, error) {
17✔
855
        id := identity.FromContext(ctx)
17✔
856
        tenantId := ""
17✔
857
        if id != nil {
34✔
858
                tenantId = id.Tenant
17✔
859
        }
17✔
860
        return getDeviceStatusDB(db, DbName, tenantId, ctx, devId)
17✔
861
}
862

863
func getDeviceStatus(statuses map[string]int) (string, error) {
31✔
864
        if statuses[model.DevStatusAccepted] > 1 || statuses[model.DevStatusPreauth] > 1 {
35✔
865
                return "", store.ErrDevStatusBroken
4✔
866
        }
4✔
867

868
        if statuses[model.DevStatusAccepted] == 1 {
36✔
869
                return model.DevStatusAccepted, nil
9✔
870
        }
9✔
871

872
        if statuses[model.DevStatusPreauth] == 1 {
21✔
873
                return model.DevStatusPreauth, nil
2✔
874
        }
2✔
875

876
        if statuses[model.DevStatusPending] > 0 {
26✔
877
                return model.DevStatusPending, nil
9✔
878
        }
9✔
879

880
        if statuses[model.DevStatusRejected] > 0 {
18✔
881
                return model.DevStatusRejected, nil
9✔
882
        }
9✔
883

NEW
884
        return "", store.ErrDevStatusBroken
×
885
}
886

887
func getDeviceStatusDB(
888
        db *DataStoreMongo,
889
        dbName string,
890
        tenantId string,
891
        ctx context.Context,
892
        devId string,
893
) (string, error) {
37✔
894
        var statuses = map[string]int{}
37✔
895

37✔
896
        c := db.client.Database(dbName).Collection(DbAuthSetColl)
37✔
897

37✔
898
        filter := model.AuthSet{
37✔
899
                DeviceId: devId,
37✔
900
        }
37✔
901
        if tenantId != "" {
54✔
902
                filter.TenantID = tenantId
17✔
903
        }
17✔
904

905
        match := bson.D{
37✔
906
                {Key: "$match", Value: filter},
37✔
907
        }
37✔
908
        group := bson.D{
37✔
909
                {Key: "$group", Value: bson.D{
37✔
910
                        {Key: "_id", Value: "$status"},
37✔
911
                        {Key: "count", Value: bson.M{"$sum": 1}}},
37✔
912
                },
37✔
913
        }
37✔
914

37✔
915
        pipeline := []bson.D{
37✔
916
                match,
37✔
917
                group,
37✔
918
        }
37✔
919
        var result []struct {
37✔
920
                Status string `bson:"_id"`
37✔
921
                Value  int    `bson:"count"`
37✔
922
        }
37✔
923
        cursor, err := c.Aggregate(ctx, pipeline)
37✔
924
        if err != nil {
37✔
925
                return "", err
×
926
        }
×
927
        if err := cursor.All(ctx, &result); err != nil {
37✔
928
                if err == mongo.ErrNoDocuments {
×
929
                        return "", store.ErrAuthSetNotFound
×
930
                }
×
931
                return "", err
×
932
        }
933

934
        if len(result) == 0 {
44✔
935
                return "", store.ErrAuthSetNotFound
7✔
936
        }
7✔
937

938
        for _, res := range result {
80✔
939
                statuses[res.Status] = res.Value
49✔
940
        }
49✔
941

942
        status, err := getDeviceStatus(statuses)
31✔
943
        if err != nil {
35✔
944
                return "", err
4✔
945
        }
4✔
946

947
        return status, nil
27✔
948
}
949

950
func (db *DataStoreMongo) ListTenantsIds(
951
        ctx context.Context,
952
) ([]string, error) {
9✔
953
        collDevs := db.client.
9✔
954
                Database(DbName).
9✔
955
                Collection(DbDevicesColl)
9✔
956

9✔
957
        results, err := collDevs.Distinct(ctx, dbFieldTenantID, bson.D{})
9✔
958
        if err != nil {
11✔
959
                return []string{}, nil
2✔
960
        }
2✔
961
        if len(results) < 1 {
7✔
NEW
962
                return []string{}, mongo.ErrNoDocuments
×
UNCOV
963
        }
×
964
        ids := make([]string, len(results))
7✔
965
        for i, id := range results {
20✔
966
                ids[i] = id.(string)
13✔
967
        }
13✔
968
        return ids, nil
7✔
969
}
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