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

mendersoftware / deviceauth / 1284672998

09 May 2024 01:19PM UTC coverage: 81.658% (-1.1%) from 82.796%
1284672998

Pull #715

gitlab-ci

alfrunes
test(acceptance/os): :broom: Remove unused fixtures

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #715: Acceptance test fixup

4808 of 5888 relevant lines covered (81.66%)

51.13 hits per line

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

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

15
package mongo
16

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

180
type DeviceFilter model.DeviceFilter
181

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

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

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

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

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

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

246
        return res, nil
12✔
247
}
248

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

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

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

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

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

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

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

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

284
        return &res, nil
4✔
285
}
286

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

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

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

305
        return &res, nil
4✔
306
}
307

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

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

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

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

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

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

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

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

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

353
        return nil
2✔
354
}
355

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

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

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

368
        return nil
2✔
369
}
370

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

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

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

386
        return nil
2✔
387
}
388

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

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

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

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

407
        return &res, nil
3✔
408
}
409

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

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

422
        return nil
3✔
423
}
424

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

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

3✔
436
        return err
3✔
437
}
438

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

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

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

460
        return nil
3✔
461
}
462

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

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

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

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

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

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

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

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

504
        return nil
68✔
505
}
506

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

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

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

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

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

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

579
        return nil
71✔
580
}
581

582
func (db *DataStoreMongo) AddAuthSet(ctx context.Context, set model.AuthSet) error {
590✔
583
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
590✔
584

590✔
585
        if set.Id == "" {
1,179✔
586
                uid := oid.NewUUIDv4()
589✔
587
                set.Id = uid.String()
589✔
588
        }
589✔
589
        id := identity.FromContext(ctx)
590✔
590
        tenantId := ""
590✔
591
        if id != nil {
1,179✔
592
                tenantId = id.Tenant
589✔
593
        }
589✔
594
        set.TenantID = tenantId
590✔
595

590✔
596
        if _, err := c.InsertOne(ctx, set); err != nil {
591✔
597
                if strings.Contains(err.Error(), "duplicate key error") {
2✔
598
                        return store.ErrObjectExists
1✔
599
                }
1✔
600
                return errors.Wrap(err, "failed to store device")
×
601
        }
602

603
        return nil
590✔
604
}
605

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

6✔
613
        id := identity.FromContext(ctx)
6✔
614
        tenantId := ""
6✔
615
        if id != nil {
10✔
616
                tenantId = id.Tenant
4✔
617
        }
4✔
618

619
        filter := model.AuthSet{
6✔
620
                IdDataSha256: idDataHash,
6✔
621
                PubKey:       key,
6✔
622
                TenantID:     tenantId,
6✔
623
        }
6✔
624
        res := model.AuthSet{}
6✔
625

6✔
626
        err := c.FindOne(ctx, filter).Decode(&res)
6✔
627
        if err != nil {
8✔
628
                if err == mongo.ErrNoDocuments {
4✔
629
                        return nil, store.ErrAuthSetNotFound
2✔
630
                } else {
2✔
631
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
632
                }
×
633
        }
634

635
        return &res, nil
4✔
636
}
637

638
func (db *DataStoreMongo) GetAuthSetByIdDataHashKeyByStatus(
639
        ctx context.Context,
640
        idDataHash []byte,
641
        key string,
642
        status string,
643
) (*model.AuthSet, error) {
3✔
644
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
3✔
645

3✔
646
        id := identity.FromContext(ctx)
3✔
647
        tenantId := ""
3✔
648
        if id != nil {
5✔
649
                tenantId = id.Tenant
2✔
650
        }
2✔
651

652
        filter := model.AuthSet{
3✔
653
                IdDataSha256: idDataHash,
3✔
654
                PubKey:       key,
3✔
655
                TenantID:     tenantId,
3✔
656
                Status:       status,
3✔
657
        }
3✔
658
        res := model.AuthSet{}
3✔
659

3✔
660
        err := c.FindOne(ctx, filter).Decode(&res)
3✔
661
        if err != nil {
5✔
662
                if err == mongo.ErrNoDocuments {
4✔
663
                        return nil, store.ErrAuthSetNotFound
2✔
664
                } else {
2✔
665
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
666
                }
×
667
        }
668

669
        return &res, nil
1✔
670
}
671

672
func (db *DataStoreMongo) GetAuthSetById(
673
        ctx context.Context,
674
        auth_id string,
675
) (*model.AuthSet, error) {
3✔
676
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
3✔
677

3✔
678
        res := model.AuthSet{}
3✔
679
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": auth_id})).Decode(&res)
3✔
680
        if err != nil {
5✔
681
                if err == mongo.ErrNoDocuments {
4✔
682
                        return nil, store.ErrAuthSetNotFound
2✔
683
                } else {
2✔
684
                        return nil, errors.Wrap(err, "failed to fetch authentication set")
×
685
                }
×
686
        }
687

688
        return &res, nil
2✔
689
}
690

691
func (db *DataStoreMongo) GetAuthSetsForDevice(
692
        ctx context.Context,
693
        devid string,
694
) ([]model.AuthSet, error) {
6✔
695
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
6✔
696

6✔
697
        res := []model.AuthSet{}
6✔
698

6✔
699
        id := identity.FromContext(ctx)
6✔
700
        tenantId := ""
6✔
701
        if id != nil {
11✔
702
                tenantId = id.Tenant
5✔
703
        }
5✔
704
        cursor, err := c.Find(ctx, model.AuthSet{DeviceId: devid, TenantID: tenantId})
6✔
705
        if err != nil {
6✔
706
                return nil, err
×
707
        }
×
708
        if err = cursor.All(ctx, &res); err != nil {
6✔
709
                if err == mongo.ErrNoDocuments {
×
710
                        return nil, store.ErrAuthSetNotFound
×
711
                }
×
712
                return nil, errors.Wrap(err, "failed to fetch authentication sets")
×
713
        }
714

715
        return res, nil
6✔
716
}
717

718
func (db *DataStoreMongo) RejectAuthSetsForDevice(
719
        ctx context.Context,
720
        deviceID string,
721
) error {
1✔
722
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
1✔
723
        var tenantID string
1✔
724
        if id := identity.FromContext(ctx); id != nil {
1✔
725
                tenantID = id.Tenant
×
726
        }
×
727

728
        filter := bson.M{
1✔
729
                model.AuthSetKeyDeviceId: deviceID,
1✔
730
                model.AuthSetKeyStatus:   model.DevStatusAccepted,
1✔
731
                dbFieldTenantID:          tenantID,
1✔
732
        }
1✔
733
        update := bson.M{
1✔
734
                "$set": bson.M{
1✔
735
                        model.AuthSetKeyStatus: model.DevStatusRejected,
1✔
736
                },
1✔
737
        }
1✔
738

1✔
739
        if res, err := c.UpdateMany(ctx, filter, update); err != nil {
1✔
740
                return errors.Wrap(err, "failed to update auth set")
×
741
        } else if res.MatchedCount == 0 {
2✔
742
                return store.ErrAuthSetNotFound
1✔
743
        }
1✔
744

745
        return nil
×
746
}
747

748
func (db *DataStoreMongo) UpdateAuthSet(
749
        ctx context.Context,
750
        filter interface{},
751
        mod model.AuthSetUpdate,
752
) error {
6✔
753
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
6✔
754

6✔
755
        update := bson.M{"$set": mod}
6✔
756

6✔
757
        if res, err := c.UpdateMany(ctx, ctxstore.WithTenantID(ctx, filter), update); err != nil {
6✔
758
                return errors.Wrap(err, "failed to update auth set")
×
759
        } else if res.MatchedCount == 0 {
8✔
760
                return store.ErrAuthSetNotFound
2✔
761
        }
2✔
762

763
        return nil
4✔
764
}
765

766
func (db *DataStoreMongo) UpdateAuthSetById(
767
        ctx context.Context,
768
        id string,
769
        mod model.AuthSetUpdate,
770
) error {
6✔
771
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
6✔
772
        res, err := c.UpdateOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": id}), bson.M{"$set": mod})
6✔
773
        if err != nil {
6✔
774
                return errors.Wrap(err, "failed to update auth set")
×
775
        }
×
776
        if res.MatchedCount == 0 {
7✔
777
                return store.ErrAuthSetNotFound
1✔
778
        }
1✔
779

780
        return nil
5✔
781
}
782

783
func (db *DataStoreMongo) DeleteAuthSetsForDevice(ctx context.Context, devid string) error {
4✔
784
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
4✔
785
        id := identity.FromContext(ctx)
4✔
786
        tenantId := ""
4✔
787
        if id != nil {
7✔
788
                tenantId = id.Tenant
3✔
789
        }
3✔
790

791
        ci, err := c.DeleteMany(
4✔
792
                ctx,
4✔
793
                model.AuthSet{
4✔
794
                        DeviceId: devid,
4✔
795
                        TenantID: tenantId,
4✔
796
                },
4✔
797
        )
4✔
798

4✔
799
        if err != nil {
4✔
800
                return errors.Wrap(err, "failed to remove authentication sets for device")
×
801
        }
×
802

803
        if ci.DeletedCount == 0 {
5✔
804
                return store.ErrAuthSetNotFound
1✔
805
        }
1✔
806

807
        return nil
3✔
808
}
809

810
func (db *DataStoreMongo) DeleteAuthSetForDevice(
811
        ctx context.Context,
812
        devId string,
813
        authId string,
814
) error {
4✔
815
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
4✔
816

4✔
817
        id := identity.FromContext(ctx)
4✔
818
        tenantId := ""
4✔
819
        if id != nil {
6✔
820
                tenantId = id.Tenant
2✔
821
        }
2✔
822
        filter := model.AuthSet{Id: authId, DeviceId: devId, TenantID: tenantId}
4✔
823
        result, err := c.DeleteOne(ctx, filter)
4✔
824
        if err != nil {
4✔
825
                return errors.Wrap(err, "failed to remove authentication set for device")
×
826
        } else if result.DeletedCount < 1 {
6✔
827
                return store.ErrAuthSetNotFound
2✔
828
        }
2✔
829

830
        return nil
2✔
831
}
832

833
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
2✔
834
        db.multitenant = true
2✔
835
        return db
2✔
836
}
2✔
837

838
func (db *DataStoreMongo) WithAutomigrate() store.DataStore {
69✔
839
        db.automigrate = true
69✔
840
        return db
69✔
841
}
69✔
842

843
func (db *DataStoreMongo) PutLimit(ctx context.Context, lim model.Limit) error {
5✔
844
        if lim.Name == "" {
6✔
845
                return errors.New("empty limit name")
1✔
846
        }
1✔
847

848
        c := db.client.Database(DbName).Collection(DbLimitsColl)
4✔
849
        id := identity.FromContext(ctx)
4✔
850
        tenantId := ""
4✔
851
        if id != nil {
8✔
852
                tenantId = id.Tenant
4✔
853
        }
4✔
854

855
        lim.TenantID = tenantId
4✔
856
        query := ctxstore.WithTenantID(ctx, bson.M{dbFieldName: lim.Name})
4✔
857

4✔
858
        updateOptions := mopts.Update()
4✔
859
        updateOptions.SetUpsert(true)
4✔
860
        if _, err := c.UpdateOne(
4✔
861
                ctx, query, bson.M{"$set": lim}, updateOptions); err != nil {
4✔
862
                return errors.Wrap(err, "failed to set or update limit")
×
863
        }
×
864

865
        return nil
4✔
866
}
867

868
func (db *DataStoreMongo) DeleteLimit(ctx context.Context, lim string) error {
1✔
869
        if lim == "" {
1✔
870
                return errors.New("empty limit name")
×
871
        }
×
872

873
        c := db.client.Database(DbName).Collection(DbLimitsColl)
1✔
874

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

1✔
877
        if _, err := c.DeleteOne(ctx, query); err != nil {
1✔
878
                return errors.Wrap(err, "failed to delete limit")
×
879
        }
×
880

881
        return nil
1✔
882
}
883

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

5✔
887
        var lim model.Limit
5✔
888

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

5✔
891
        if err != nil {
8✔
892
                if err == mongo.ErrNoDocuments {
6✔
893
                        return nil, store.ErrLimitNotFound
3✔
894
                } else {
3✔
895
                        return nil, errors.Wrap(err, "failed to fetch limit")
×
896
                }
×
897
        }
898

899
        return &lim, nil
3✔
900
}
901

902
func (db *DataStoreMongo) GetDevCountByStatus(ctx context.Context, status string) (int, error) {
85✔
903
        var (
85✔
904
                fltr     = bson.D{}
85✔
905
                devsColl = db.client.
85✔
906
                                Database(DbName).
85✔
907
                                Collection(DbDevicesColl)
85✔
908
        )
85✔
909

85✔
910
        if status != "" {
156✔
911
                fltr = bson.D{{Key: "status", Value: status}}
71✔
912
        }
71✔
913
        count, err := devsColl.CountDocuments(ctx, ctxstore.WithTenantID(ctx, fltr))
85✔
914
        if err != nil {
85✔
915
                return 0, err
×
916
        }
×
917
        return int(count), nil
85✔
918
}
919

920
func (db *DataStoreMongo) GetDeviceStatus(ctx context.Context, devId string) (string, error) {
9✔
921
        id := identity.FromContext(ctx)
9✔
922
        tenantId := ""
9✔
923
        if id != nil {
17✔
924
                tenantId = id.Tenant
8✔
925
        }
8✔
926
        return getDeviceStatusDB(db, DbName, tenantId, ctx, devId)
9✔
927
}
928

929
func getDeviceStatus(statuses map[string]int) (string, error) {
16✔
930
        if statuses[model.DevStatusAccepted] > 1 || statuses[model.DevStatusPreauth] > 1 {
18✔
931
                return "", store.ErrDevStatusBroken
2✔
932
        }
2✔
933

934
        if statuses[model.DevStatusAccepted] == 1 {
19✔
935
                return model.DevStatusAccepted, nil
5✔
936
        }
5✔
937

938
        if statuses[model.DevStatusPreauth] == 1 {
11✔
939
                return model.DevStatusPreauth, nil
1✔
940
        }
1✔
941

942
        if statuses[model.DevStatusPending] > 0 {
14✔
943
                return model.DevStatusPending, nil
5✔
944
        }
5✔
945

946
        if statuses[model.DevStatusRejected] > 0 {
10✔
947
                return model.DevStatusRejected, nil
5✔
948
        }
5✔
949

950
        return "", store.ErrDevStatusBroken
×
951
}
952

953
func getDeviceStatusDB(
954
        db *DataStoreMongo,
955
        dbName string,
956
        tenantId string,
957
        ctx context.Context,
958
        devId string,
959
) (string, error) {
19✔
960
        var statuses = map[string]int{}
19✔
961

19✔
962
        c := db.client.Database(dbName).Collection(DbAuthSetColl)
19✔
963

19✔
964
        filter := model.AuthSet{
19✔
965
                DeviceId: devId,
19✔
966
        }
19✔
967
        if tenantId != "" {
27✔
968
                filter.TenantID = tenantId
8✔
969
        }
8✔
970

971
        match := bson.D{
19✔
972
                {Key: "$match", Value: filter},
19✔
973
        }
19✔
974
        group := bson.D{
19✔
975
                {Key: "$group", Value: bson.D{
19✔
976
                        {Key: "_id", Value: "$status"},
19✔
977
                        {Key: "count", Value: bson.M{"$sum": 1}}},
19✔
978
                },
19✔
979
        }
19✔
980

19✔
981
        pipeline := []bson.D{
19✔
982
                match,
19✔
983
                group,
19✔
984
        }
19✔
985
        var result []struct {
19✔
986
                Status string `bson:"_id"`
19✔
987
                Value  int    `bson:"count"`
19✔
988
        }
19✔
989
        cursor, err := c.Aggregate(ctx, pipeline)
19✔
990
        if err != nil {
19✔
991
                return "", err
×
992
        }
×
993
        if err := cursor.All(ctx, &result); err != nil {
19✔
994
                if err == mongo.ErrNoDocuments {
×
995
                        return "", store.ErrAuthSetNotFound
×
996
                }
×
997
                return "", err
×
998
        }
999

1000
        if len(result) == 0 {
23✔
1001
                return "", store.ErrAuthSetNotFound
4✔
1002
        }
4✔
1003

1004
        for _, res := range result {
41✔
1005
                statuses[res.Status] = res.Value
25✔
1006
        }
25✔
1007

1008
        status, err := getDeviceStatus(statuses)
16✔
1009
        if err != nil {
18✔
1010
                return "", err
2✔
1011
        }
2✔
1012

1013
        return status, nil
14✔
1014
}
1015

1016
func (db *DataStoreMongo) ListTenantsIds(
1017
        ctx context.Context,
1018
) ([]string, error) {
4✔
1019
        collDevs := db.client.
4✔
1020
                Database(DbName).
4✔
1021
                Collection(DbDevicesColl)
4✔
1022

4✔
1023
        results, err := collDevs.Distinct(ctx, dbFieldTenantID, bson.D{})
4✔
1024
        if err != nil {
5✔
1025
                return []string{}, nil
1✔
1026
        }
1✔
1027
        if len(results) < 1 {
3✔
1028
                return []string{}, mongo.ErrNoDocuments
×
1029
        }
×
1030
        ids := make([]string, len(results))
3✔
1031
        for i, id := range results {
9✔
1032
                ids[i] = id.(string)
6✔
1033
        }
6✔
1034
        return ids, nil
3✔
1035
}
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