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

mendersoftware / deviceauth / 826658230

pending completion
826658230

Pull #638

gitlab-ci

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

334 of 405 new or added lines in 5 files covered. (82.47%)

38 existing lines in 3 files now uncovered.

4669 of 5588 relevant lines covered (83.55%)

75.19 hits per line

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

89.63
/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
        dbFieldName         = "name"
56
)
57

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

178
type DeviceFilter model.DeviceFilter
179

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

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

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

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

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

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

241
        return res, nil
23✔
242
}
243

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

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

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

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

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

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

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

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

279
        return &res, nil
7✔
280
}
281

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

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

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

300
        return &res, nil
7✔
301
}
302

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

693✔
305
        if d.Id == "" {
696✔
306
                uid := oid.NewUUIDv4()
3✔
307
                d.Id = uid.String()
3✔
308
        }
3✔
309
        id := identity.FromContext(ctx)
693✔
310
        tenantId := ""
693✔
311
        if id != nil {
1,386✔
312
                tenantId = id.Tenant
693✔
313
        }
693✔
314
        d.TenantID = tenantId
693✔
315

693✔
316
        c := db.client.Database(DbName).Collection(DbDevicesColl)
693✔
317

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

327
func (db *DataStoreMongo) UpdateDevice(ctx context.Context,
328
        deviceID string, updev model.DeviceUpdate) error {
7✔
329

7✔
330
        c := db.client.Database(DbName).Collection(DbDevicesColl)
7✔
331

7✔
332
        id := identity.FromContext(ctx)
7✔
333
        tenantId := ""
7✔
334
        if id != nil {
12✔
335
                tenantId = id.Tenant
5✔
336
        }
5✔
337
        updev.TenantID = tenantId
7✔
338
        updev.UpdatedTs = uto.TimePtr(time.Now().UTC())
7✔
339
        update := bson.M{
7✔
340
                "$inc": bson.M{
7✔
341
                        DbKeyDeviceRevision: 1,
7✔
342
                },
7✔
343
                "$set": updev,
7✔
344
        }
7✔
345

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

353
        return nil
3✔
354
}
355

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

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

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

368
        return nil
3✔
369
}
370

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

3✔
375
        filter := ctxstore.WithTenantID(ctx, bson.M{"_id": t.Claims.ID})
3✔
376
        id := identity.FromContext(ctx)
3✔
377
        tenantId := ""
3✔
378
        if id != nil {
6✔
379
                tenantId = id.Tenant
3✔
380
        }
3✔
381
        t.TenantID = tenantId
3✔
382
        update := bson.M{"$set": t}
3✔
383
        updateOpts := mopts.Update()
3✔
384
        updateOpts.SetUpsert(true)
3✔
385

3✔
386
        if _, err := collTokens.UpdateOne(
3✔
387
                ctx, filter, update, updateOpts,
3✔
388
        ); err != nil {
3✔
389
                return errors.Wrap(err, "failed to store token")
×
390
        }
×
391

392
        return nil
3✔
393
}
394

395
func (db *DataStoreMongo) GetToken(
396
        ctx context.Context,
397
        jti oid.ObjectID,
398
) (*jwt.Token, error) {
9✔
399

9✔
400
        c := db.client.Database(DbName).Collection(DbTokensColl)
9✔
401

9✔
402
        res := jwt.Token{}
9✔
403

9✔
404
        err := c.FindOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": jti})).Decode(&res)
9✔
405
        if err != nil {
14✔
406
                if err == mongo.ErrNoDocuments {
10✔
407
                        return nil, store.ErrTokenNotFound
5✔
408
                } else {
5✔
409
                        return nil, errors.Wrap(err, "failed to fetch token")
×
410
                }
×
411
        }
412

413
        return &res, nil
5✔
414
}
415

416
func (db *DataStoreMongo) DeleteToken(ctx context.Context, jti oid.ObjectID) error {
9✔
417
        database := db.client.Database(DbName)
9✔
418
        collTokens := database.Collection(DbTokensColl)
9✔
419

9✔
420
        filter := ctxstore.WithTenantID(ctx, bson.M{"_id": jti})
9✔
421
        result, err := collTokens.DeleteOne(ctx, filter)
9✔
422
        if err != nil {
9✔
423
                return errors.Wrap(err, "failed to remove token")
×
424
        } else if result.DeletedCount < 1 {
13✔
425
                return store.ErrTokenNotFound
4✔
426
        }
4✔
427

428
        return nil
5✔
429
}
430

431
func (db *DataStoreMongo) DeleteTokens(ctx context.Context) error {
7✔
432
        c := db.client.Database(DbName).
7✔
433
                Collection(DbTokensColl)
7✔
434
        _, err := c.DeleteMany(ctx, ctxstore.WithTenantID(ctx, bson.D{}))
7✔
435

7✔
436
        return err
7✔
437
}
7✔
438

439
func (db *DataStoreMongo) DeleteTokenByDevId(
440
        ctx context.Context,
441
        devID oid.ObjectID,
442
) error {
7✔
443
        c := db.client.Database(DbName).
7✔
444
                Collection(DbTokensColl)
7✔
445
        ci, err := c.DeleteMany(ctx, ctxstore.WithTenantID(ctx, bson.M{"sub": devID}))
7✔
446

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

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

455
        return nil
5✔
456
}
457

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

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

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

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

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

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

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

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

499
        return nil
133✔
500
}
501

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

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

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

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

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

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

574
        return nil
139✔
575
}
576

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

1,205✔
580
        if set.Id == "" {
2,409✔
581
                uid := oid.NewUUIDv4()
1,204✔
582
                set.Id = uid.String()
1,204✔
583
        }
1,204✔
584
        id := identity.FromContext(ctx)
1,205✔
585
        tenantId := ""
1,205✔
586
        if id != nil {
2,410✔
587
                tenantId = id.Tenant
1,205✔
588
        }
1,205✔
589
        set.TenantID = tenantId
1,205✔
590

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

598
        return nil
1,205✔
599
}
600

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

11✔
608
        id := identity.FromContext(ctx)
11✔
609
        tenantId := ""
11✔
610
        if id != nil {
20✔
611
                tenantId = id.Tenant
9✔
612
        }
9✔
613
        filter := model.AuthSet{
11✔
614
                IdDataSha256: idDataHash,
11✔
615
                PubKey:       key,
11✔
616
                TenantID:     tenantId,
11✔
617
        }
11✔
618
        res := model.AuthSet{}
11✔
619

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

629
        return &res, nil
7✔
630
}
631

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

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

648
        return &res, nil
3✔
649
}
650

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

11✔
657
        res := []model.AuthSet{}
11✔
658

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

675
        return res, nil
11✔
676
}
677

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

13✔
685
        id := identity.FromContext(ctx)
13✔
686
        tenantId := ""
13✔
687
        if id != nil {
26✔
688
                tenantId = id.Tenant
13✔
689
        }
13✔
690
        mod.TenantID = tenantId
13✔
691
        update := bson.M{"$set": mod}
13✔
692

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

699
        return nil
9✔
700
}
701

702
func (db *DataStoreMongo) UpdateAuthSetById(
703
        ctx context.Context,
704
        id string,
705
        mod model.AuthSetUpdate,
706
) error {
11✔
707
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
11✔
708
        identity := identity.FromContext(ctx)
11✔
709
        tenantId := ""
11✔
710
        if identity != nil {
16✔
711
                tenantId = identity.Tenant
5✔
712
        }
5✔
713
        mod.TenantID = tenantId
11✔
714
        res, err := c.UpdateOne(ctx, ctxstore.WithTenantID(ctx, bson.M{"_id": id}), bson.M{"$set": mod})
11✔
715
        if err != nil {
11✔
716
                return errors.Wrap(err, "failed to update auth set")
×
717
        }
×
718
        if res.MatchedCount == 0 {
13✔
719
                return store.ErrAuthSetNotFound
2✔
720
        }
2✔
721

722
        return nil
9✔
723
}
724

725
func (db *DataStoreMongo) DeleteAuthSetsForDevice(ctx context.Context, devid string) error {
7✔
726
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
7✔
727

7✔
728
        id := identity.FromContext(ctx)
7✔
729
        tenantId := ""
7✔
730
        if id != nil {
13✔
731
                tenantId = id.Tenant
6✔
732
        }
6✔
733
        ci, err := c.DeleteMany(ctx, model.AuthSet{DeviceId: devid, TenantID: tenantId})
7✔
734

7✔
735
        if err != nil {
7✔
736
                return errors.Wrap(err, "failed to remove authentication sets for device")
×
737
        }
×
738

739
        if ci.DeletedCount == 0 {
9✔
740
                return store.ErrAuthSetNotFound
2✔
741
        }
2✔
742

743
        return nil
5✔
744
}
745

746
func (db *DataStoreMongo) DeleteAuthSetForDevice(
747
        ctx context.Context,
748
        devId string,
749
        authId string,
750
) error {
7✔
751
        c := db.client.Database(DbName).Collection(DbAuthSetColl)
7✔
752

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

766
        return nil
3✔
767
}
768

769
func (db *DataStoreMongo) WithMultitenant() *DataStoreMongo {
5✔
770
        db.multitenant = true
5✔
771
        return db
5✔
772
}
5✔
773

774
func (db *DataStoreMongo) WithAutomigrate() store.DataStore {
135✔
775
        return &DataStoreMongo{
135✔
776
                client:      db.client,
135✔
777
                automigrate: true,
135✔
778
        }
135✔
779
}
135✔
780

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

786
        c := db.client.Database(DbName).Collection(DbLimitsColl)
7✔
787
        id := identity.FromContext(ctx)
7✔
788
        tenantId := ""
7✔
789
        if id != nil {
14✔
790
                tenantId = id.Tenant
7✔
791
        }
7✔
792

793
        lim.TenantID = tenantId
7✔
794
        query := ctxstore.WithTenantID(ctx, bson.M{dbFieldName: lim.Name})
7✔
795

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

803
        return nil
7✔
804
}
805

806
func (db *DataStoreMongo) DeleteLimit(ctx context.Context, lim string) error {
2✔
807
        if lim == "" {
2✔
808
                return errors.New("empty limit name")
×
809
        }
×
810

811
        c := db.client.Database(DbName).Collection(DbLimitsColl)
2✔
812

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

2✔
815
        if _, err := c.DeleteOne(ctx, query); err != nil {
2✔
816
                return errors.Wrap(err, "failed to delete limit")
×
817
        }
×
818

819
        return nil
2✔
820
}
821

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

9✔
825
        var lim model.Limit
9✔
826

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

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

837
        return &lim, nil
5✔
838
}
839

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

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

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

867
func getDeviceStatus(statuses map[string]int) (string, error) {
31✔
868
        if statuses[model.DevStatusAccepted] > 1 || statuses[model.DevStatusPreauth] > 1 {
35✔
869
                return "", store.ErrDevStatusBroken
4✔
870
        }
4✔
871

872
        if statuses[model.DevStatusAccepted] == 1 {
36✔
873
                return model.DevStatusAccepted, nil
9✔
874
        }
9✔
875

876
        if statuses[model.DevStatusPreauth] == 1 {
21✔
877
                return model.DevStatusPreauth, nil
2✔
878
        }
2✔
879

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

884
        if statuses[model.DevStatusRejected] > 0 {
18✔
885
                return model.DevStatusRejected, nil
9✔
886
        }
9✔
887

NEW
888
        return "", store.ErrDevStatusBroken
×
889
}
890

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

37✔
900
        c := db.client.Database(dbName).Collection(DbAuthSetColl)
37✔
901

37✔
902
        filter := model.AuthSet{
37✔
903
                DeviceId: devId,
37✔
904
        }
37✔
905
        if tenantId != "" {
54✔
906
                filter.TenantID = tenantId
17✔
907
        }
17✔
908

909
        match := bson.D{
37✔
910
                {Key: "$match", Value: filter},
37✔
911
        }
37✔
912
        group := bson.D{
37✔
913
                {Key: "$group", Value: bson.D{
37✔
914
                        {Key: "_id", Value: "$status"},
37✔
915
                        {Key: "count", Value: bson.M{"$sum": 1}}},
37✔
916
                },
37✔
917
        }
37✔
918

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

938
        if len(result) == 0 {
44✔
939
                return "", store.ErrAuthSetNotFound
7✔
940
        }
7✔
941

942
        for _, res := range result {
80✔
943
                statuses[res.Status] = res.Value
49✔
944
        }
49✔
945

946
        status, err := getDeviceStatus(statuses)
31✔
947
        if err != nil {
35✔
948
                return "", err
4✔
949
        }
4✔
950

951
        return status, nil
27✔
952
}
953

954
func (db *DataStoreMongo) ListTenantsIds(
955
        ctx context.Context,
956
) ([]string, error) {
9✔
957
        collDevs := db.client.
9✔
958
                Database(DbName).
9✔
959
                Collection(DbDevicesColl)
9✔
960

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