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

mendersoftware / deviceauth / 948596009

pending completion
948596009

push

gitlab-ci

web-flow
Merge pull request #655 from merlin-northern/men_6529_merge_single_db_to_master

Merge single db to master

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

16 existing lines in 2 files now uncovered.

4809 of 5767 relevant lines covered (83.39%)

48.84 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 {
87✔
88
        return &DataStoreMongo{
87✔
89
                client: client,
87✔
90
        }
87✔
91
}
87✔
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 {
5✔
131
        var (
5✔
132
                dbCtx   context.Context
5✔
133
                err     error
5✔
134
                errChan = make(chan error, 1)
5✔
135
        )
5✔
136
        tenantsIds, err := db.ListTenantsIds(ctx)
5✔
137
        if err != nil {
5✔
NEW
138
                return errors.Wrap(err, "store: failed to retrieve tenants")
×
UNCOV
139
        }
×
140
        go func() {
10✔
141
                for _, tenantID := range tenantsIds {
12✔
142
                        if ctx.Err() != nil {
8✔
143
                                return
1✔
144
                        }
1✔
145
                        if tenantID != "" {
11✔
146
                                dbCtx = identity.WithContext(ctx,
5✔
147
                                        &identity.Identity{
5✔
148
                                                Tenant: tenantID,
5✔
149
                                        },
5✔
150
                                )
5✔
151
                        } else {
6✔
152
                                dbCtx = ctx
1✔
153
                        }
1✔
154
                        err := mapFunc(dbCtx)
6✔
155
                        if err != nil {
7✔
156
                                errChan <- errors.Wrapf(err,
1✔
157
                                        `store: failed to apply mapFunc to tenant "%s"`,
1✔
158
                                        tenantID,
1✔
159
                                )
1✔
160
                        }
1✔
161
                }
162
                errChan <- nil
4✔
163
        }()
164

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

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

179
type DeviceFilter model.DeviceFilter
180

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

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

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

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

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

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

242
        return res, nil
12✔
243
}
244

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

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

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

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

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

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

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

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

280
        return &res, nil
4✔
281
}
282

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

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

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

301
        return &res, nil
4✔
302
}
303

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

347✔
306
        if d.Id == "" {
349✔
307
                uid := oid.NewUUIDv4()
2✔
308
                d.Id = uid.String()
2✔
309
        }
2✔
310

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

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

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

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

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

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

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

349
        return nil
2✔
350
}
351

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

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

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

364
        return nil
2✔
365
}
366

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

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

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

382
        return nil
2✔
383
}
384

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

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

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

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

403
        return &res, nil
3✔
404
}
405

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

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

418
        return nil
3✔
419
}
420

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

4✔
425
        id := identity.FromContext(ctx)
4✔
426
        filter := bson.M{dbFieldTenantClaim: nil}
4✔
427
        if id != nil && id.Tenant != "" {
6✔
428
                filter[dbFieldTenantClaim] = id.Tenant
2✔
429
        }
2✔
430
        _, err := c.DeleteMany(ctx, filter)
4✔
431

4✔
432
        return err
4✔
433
}
434

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

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

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

456
        return nil
3✔
457
}
458

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

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

70✔
464
        if db.multitenant {
73✔
465
                l.Infof("running migrations in multitenant mode")
3✔
466

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

476
        if db.automigrate {
138✔
477
                l.Infof("automigrate is ON, will apply migrations")
68✔
478
        } else {
70✔
479
                l.Infof("automigrate is OFF, will check db version compatibility")
2✔
480
        }
2✔
481

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

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

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

500
        return nil
67✔
501
}
502

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

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

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

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

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

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

575
        return nil
70✔
576
}
577

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

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

597✔
592
        if _, err := c.InsertOne(ctx, set); err != nil {
598✔
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
597✔
600
}
601

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

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

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

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

631
        return &res, nil
4✔
632
}
633

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

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

650
        return &res, nil
2✔
651
}
652

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

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

6✔
661
        id := identity.FromContext(ctx)
6✔
662
        tenantId := ""
6✔
663
        if id != nil {
12✔
664
                tenantId = id.Tenant
6✔
665
        }
6✔
666
        cursor, err := c.Find(ctx, model.AuthSet{DeviceId: devid, TenantID: tenantId})
6✔
667
        if err != nil {
6✔
668
                return nil, err
×
669
        }
×
670
        if err = cursor.All(ctx, &res); err != nil {
6✔
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
6✔
678
}
679

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

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

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

695
        return nil
5✔
696
}
697

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

712
        return nil
5✔
713
}
714

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

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

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

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

739
        return nil
3✔
740
}
741

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

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

762
        return nil
2✔
763
}
764

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

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

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

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

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

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

799
        return nil
4✔
800
}
801

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

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

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

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

815
        return nil
1✔
816
}
817

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

5✔
821
        var lim model.Limit
5✔
822

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

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

833
        return &lim, nil
3✔
834
}
835

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

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

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

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

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

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

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

880
        if statuses[model.DevStatusRejected] > 0 {
10✔
881
                return model.DevStatusRejected, nil
5✔
882
        }
5✔
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) {
19✔
894
        var statuses = map[string]int{}
19✔
895

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

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

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

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

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

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

942
        status, err := getDeviceStatus(statuses)
16✔
943
        if err != nil {
18✔
944
                return "", err
2✔
945
        }
2✔
946

947
        return status, nil
14✔
948
}
949

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

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