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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

88.84
/backend/services/useradm/user/useradm.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 useradm
16

17
import (
18
        "context"
19
        "time"
20

21
        "github.com/pkg/errors"
22
        "golang.org/x/crypto/bcrypt"
23

24
        "github.com/mendersoftware/mender-server/pkg/apiclient"
25
        "github.com/mendersoftware/mender-server/pkg/identity"
26
        "github.com/mendersoftware/mender-server/pkg/log"
27
        "github.com/mendersoftware/mender-server/pkg/mongo/oid"
28

29
        "github.com/mendersoftware/mender-server/services/useradm/client/tenant"
30
        "github.com/mendersoftware/mender-server/services/useradm/common"
31
        "github.com/mendersoftware/mender-server/services/useradm/jwt"
32
        "github.com/mendersoftware/mender-server/services/useradm/model"
33
        "github.com/mendersoftware/mender-server/services/useradm/scope"
34
        "github.com/mendersoftware/mender-server/services/useradm/store"
35
        "github.com/mendersoftware/mender-server/services/useradm/utils"
36
)
37

38
var (
39
        ErrUserNotFound           = store.ErrUserNotFound
40
        ErrDuplicateEmail         = store.ErrDuplicateEmail
41
        ErrETagMismatch           = errors.New("entity tag did not match any records")
42
        ErrUnauthorized           = errors.New("unauthorized")
43
        ErrAuthExpired            = errors.New("token expired")
44
        ErrAuthInvalid            = errors.New("token is invalid")
45
        ErrTenantAccountSuspended = errors.New("tenant account suspended")
46
        ErrInvalidTenantID        = errors.New("invalid tenant id")
47
        ErrTooManyTokens          = errors.New(
48
                "maximum number of personal acess tokens reached for this user")
49
        ErrDuplicateTokenName = errors.New(
50
                "Personal Access Token with a given name already exists")
51
        // password mismatch
52
        ErrCurrentPasswordMismatch = errors.New("current password mismatch")
53
        // modification of other user's password is not allowed
54
        ErrCannotModifyPassword = errors.New("password cannot be modified")
55
        // password too similar to the email
56
        ErrPassAndMailTooSimilar = errors.New("password is too similar to the email")
57
)
58

59
const (
60
        TenantStatusSuspended = "suspended"
61
        userIdMe              = "me"
62
)
63

64
//go:generate ../../../utils/mockgen.sh
65
type App interface {
66
        HealthCheck(ctx context.Context) error
67
        // Login accepts email/password, returns JWT
68
        Login(ctx context.Context, email model.Email, pass string,
69
                options *LoginOptions) (*jwt.Token, error)
70
        Logout(ctx context.Context, token *jwt.Token) error
71
        CreateUser(ctx context.Context, u *model.User) error
72
        CreateUserInternal(ctx context.Context, u *model.UserInternal) error
73
        UpdateUser(ctx context.Context, id string, u *model.UserUpdate) error
74
        Verify(ctx context.Context, token *jwt.Token) error
75
        GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error)
76
        GetUser(ctx context.Context, id string) (*model.User, error)
77
        DeleteUser(ctx context.Context, id string) error
78
        SetPassword(ctx context.Context, u model.UserUpdate) error
79

80
        // SignToken generates a signed
81
        // token using configuration & method set up in UserAdmApp
82
        SignToken(ctx context.Context, t *jwt.Token) (string, error)
83
        DeleteToken(ctx context.Context, id string) error
84

85
        // IssuePersonalAccessToken issues Personal Access Token
86
        IssuePersonalAccessToken(ctx context.Context, tr *model.TokenRequest) (string, error)
87
        // GetPersonalAccessTokens returns list of Personal Access Tokens
88
        GetPersonalAccessTokens(ctx context.Context, userID string) ([]model.PersonalAccessToken, error)
89

90
        DeleteTokens(ctx context.Context, tenantId, userId string) error
91

92
        CreateTenant(ctx context.Context, tenant model.NewTenant) error
93
        GetPlans(ctx context.Context, skip, limit int) []model.Plan
94
        GetPlanBinding(ctx context.Context) (*model.PlanBindingDetails, error)
95
}
96

97
type Config struct {
98
        // token issuer
99
        Issuer string
100
        // token expiration time
101
        ExpirationTimeSeconds int64
102
        // maximum number of log in tokens per user
103
        // zero means no limit
104
        LimitSessionsPerUser int
105
        // maximum number of personal access tokens per user
106
        // zero means no limit
107
        LimitTokensPerUser int
108
        // how often we should update personal access token
109
        // with last used timestamp
110
        TokenLastUsedUpdateFreqMinutes int
111
        // path to the private key, used to generate kid header in JWT and to get all the keys
112
        PrivateKeyPath string
113
        // PrivateKeyFileNamePattern holds the regular expression used
114
        // to get the key id from a filename
115
        PrivateKeyFileNamePattern string
116
}
117

118
type ApiClientGetter func() apiclient.HttpRunner
119

120
func simpleApiClientGetter() apiclient.HttpRunner {
1✔
121
        return &apiclient.HttpApi{}
1✔
122
}
1✔
123

124
type UserAdm struct {
125
        // JWT serialized/deserializer
126
        jwtHandlers  map[int]jwt.Handler
127
        db           store.DataStore
128
        config       Config
129
        verifyTenant bool
130
        cTenant      tenant.ClientRunner
131
        clientGetter ApiClientGetter
132
}
133

134
func NewUserAdm(jwtHandlers map[int]jwt.Handler, db store.DataStore, config Config) *UserAdm {
3✔
135

3✔
136
        return &UserAdm{
3✔
137
                jwtHandlers:  jwtHandlers,
3✔
138
                db:           db,
3✔
139
                config:       config,
3✔
140
                clientGetter: simpleApiClientGetter,
3✔
141
        }
3✔
142
}
3✔
143

144
func (u *UserAdm) HealthCheck(ctx context.Context) error {
1✔
145
        err := u.db.Ping(ctx)
1✔
146
        if err != nil {
2✔
147
                return errors.Wrap(err, "error reaching MongoDB")
1✔
148
        }
1✔
149

150
        if u.verifyTenant {
2✔
151
                err = u.cTenant.CheckHealth(ctx)
1✔
152
                if err != nil {
2✔
153
                        return errors.Wrap(err, "Tenantadm service unhealthy")
1✔
154
                }
1✔
155
        }
156

157
        return nil
1✔
158
}
159

160
func (u *UserAdm) Login(ctx context.Context, email model.Email, pass string,
161
        options *LoginOptions) (*jwt.Token, error) {
1✔
162
        var ident identity.Identity
1✔
163
        l := log.FromContext(ctx)
1✔
164

1✔
165
        if email == "" {
1✔
166
                return nil, ErrUnauthorized
×
167
        }
×
168

169
        if u.verifyTenant {
2✔
170
                // check the user's tenant
1✔
171
                tenant, err := u.cTenant.GetTenant(ctx, string(email), u.clientGetter())
1✔
172

1✔
173
                if err != nil {
2✔
174
                        return nil, errors.Wrap(err, "failed to check user's tenant")
1✔
175
                }
1✔
176

177
                if tenant == nil {
2✔
178
                        return nil, ErrUnauthorized
1✔
179
                }
1✔
180

181
                if tenant.Status == TenantStatusSuspended {
2✔
182
                        return nil, ErrTenantAccountSuspended
1✔
183
                }
1✔
184

185
                ident.Tenant = tenant.ID
1✔
186
                ctx = identity.WithContext(ctx, &ident)
1✔
187
        }
188

189
        //get user
190
        user, err := u.db.GetUserByEmail(ctx, email)
1✔
191

1✔
192
        if user == nil && err == nil {
2✔
193
                return nil, ErrUnauthorized
1✔
194
        }
1✔
195

196
        if err != nil {
2✔
197
                return nil, errors.Wrap(err, "useradm: failed to get user")
1✔
198
        }
1✔
199

200
        //verify password
201
        err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(pass))
1✔
202
        if err != nil {
2✔
203
                return nil, ErrUnauthorized
1✔
204
        }
1✔
205

206
        //generate and save token
207
        t, err := u.generateToken(
1✔
208
                user.ID,
1✔
209
                scope.All,
1✔
210
                ident.Tenant,
1✔
211
                options.NoExpiry,
1✔
212
                common.KeyIdFromPath(u.config.PrivateKeyPath, u.config.PrivateKeyFileNamePattern),
1✔
213
        )
1✔
214
        if err != nil {
1✔
215
                return nil, errors.Wrap(err, "useradm: failed to generate token")
×
216
        }
×
217

218
        err = u.db.SaveToken(ctx, t)
1✔
219
        if err != nil {
2✔
220
                return nil, errors.Wrap(err, "useradm: failed to save token")
1✔
221
        }
1✔
222
        if u.config.LimitSessionsPerUser > 0 {
2✔
223
                err = u.db.EnsureSessionTokensLimit(ctx, t.Subject, u.config.LimitSessionsPerUser)
1✔
224
                if err != nil {
2✔
225
                        return nil, errors.Wrap(err, "useradm: failed to ensure session tokens limit")
1✔
226
                }
1✔
227
        }
228

229
        if err = u.db.UpdateLoginTs(ctx, user.ID); err != nil {
2✔
230
                l.Warnf("failed to update login timestamp: %s", err.Error())
1✔
231
        }
1✔
232

233
        return t, nil
1✔
234
}
235

236
func (u *UserAdm) generateToken(subject, scope, tenant string,
237
        noExpiry bool, keyId int) (*jwt.Token, error) {
1✔
238
        id := oid.NewUUIDv4()
1✔
239
        subjectID := oid.FromString(subject)
1✔
240
        now := jwt.Time{Time: time.Now()}
1✔
241
        ret := &jwt.Token{
1✔
242
                KeyId: keyId,
1✔
243
                Claims: jwt.Claims{
1✔
244
                        ID:        id,
1✔
245
                        Subject:   subjectID,
1✔
246
                        Issuer:    u.config.Issuer,
1✔
247
                        IssuedAt:  now,
1✔
248
                        NotBefore: now,
1✔
249
                        Tenant:    tenant,
1✔
250
                        Scope:     scope,
1✔
251
                        User:      true,
1✔
252
                }}
1✔
253
        if !noExpiry {
2✔
254
                ret.Claims.ExpiresAt = &jwt.Time{
1✔
255
                        Time: now.Add(time.Second * time.Duration(u.config.ExpirationTimeSeconds)),
1✔
256
                }
1✔
257
        }
1✔
258
        return ret, ret.Claims.Valid()
1✔
259
}
260

261
func (u *UserAdm) SignToken(ctx context.Context, t *jwt.Token) (string, error) {
1✔
262
        if _, ok := u.jwtHandlers[t.KeyId]; !ok {
1✔
263
                return "", common.ErrKeyIdNotFound
×
264
        }
×
265
        return u.jwtHandlers[t.KeyId].ToJWT(t)
1✔
266
}
267

268
func (u *UserAdm) Logout(ctx context.Context, token *jwt.Token) error {
1✔
269
        return u.db.DeleteToken(ctx, token.Subject, token.ID)
1✔
270
}
1✔
271

272
func (ua *UserAdm) CreateUser(ctx context.Context, u *model.User) error {
3✔
273
        if utils.CheckIfPassSimilarToEmailRaw(string(u.Email), string(u.Password)) {
4✔
274
                return ErrPassAndMailTooSimilar
1✔
275
        }
1✔
276
        hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
3✔
277
        if err != nil {
3✔
278
                return errors.Wrap(err, "failed to generate password hash")
×
279
        }
×
280
        u.Password = string(hash)
3✔
281

3✔
282
        return ua.doCreateUser(ctx, u, true)
3✔
283
}
284

285
func (ua *UserAdm) CreateUserInternal(ctx context.Context, u *model.UserInternal) error {
×
286
        if u.PasswordHash != "" {
×
287
                u.Password = u.PasswordHash
×
288
        } else {
×
289
                hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
×
290
                if err != nil {
×
291
                        return errors.Wrap(err, "failed to generate password hash")
×
292
                }
×
293
                u.Password = string(hash)
×
294
        }
295

296
        return ua.doCreateUser(ctx, &u.User, u.ShouldPropagate())
×
297
}
298

299
func (ua *UserAdm) doCreateUser(ctx context.Context, u *model.User, propagate bool) error {
3✔
300
        var tenantErr error
3✔
301

3✔
302
        if u.ID == "" {
6✔
303
                //technically one of the functions down the oid.NewUUIDv4() stack can panic,
3✔
304
                //so what we could do is: wrap around it and recover and fall back
3✔
305
                //to non-rand based generation (NewUUIDv5(u.Email)).
3✔
306
                //on the other hand if rand failed maybe it is a good idea t not continue
3✔
307
                //after all.
3✔
308
                //the previous call to NewUUIDv5 produced exact the same ids for same
3✔
309
                //emails, which leads to problems once user changes the email address,
3✔
310
                //and then the old email is used in the new user creation.
3✔
311
                id := oid.NewUUIDv4()
3✔
312
                u.ID = id.String()
3✔
313
        }
3✔
314

315
        id := identity.FromContext(ctx)
3✔
316
        if ua.verifyTenant && propagate {
4✔
317
                tenantErr = ua.cTenant.CreateUser(ctx,
1✔
318
                        &tenant.User{
1✔
319
                                ID:       u.ID,
1✔
320
                                Name:     string(u.Email),
1✔
321
                                TenantID: id.Tenant,
1✔
322
                        },
1✔
323
                        ua.clientGetter())
1✔
324

1✔
325
                if tenantErr != nil && tenantErr != tenant.ErrDuplicateUser {
2✔
326
                        return errors.Wrap(tenantErr, "useradm: failed to create user in tenantadm")
1✔
327
                }
1✔
328
        }
329

330
        if tenantErr == tenant.ErrDuplicateUser {
4✔
331
                // check if the user exists in useradm
1✔
332
                // if the user does not exists then we should try to remove the user from tenantadm
1✔
333
                user, err := ua.db.GetUserByEmail(ctx, u.Email)
1✔
334
                if err != nil {
2✔
335
                        return errors.Wrap(err, "tenant data out of sync: failed to get user from db")
1✔
336
                }
1✔
337
                if user == nil {
2✔
338
                        if compensateErr := ua.compensateTenantUser(
1✔
339
                                ctx,
1✔
340
                                u.ID,
1✔
341
                                id.Tenant,
1✔
342
                        ); compensateErr != nil {
2✔
343
                                tenantErr = compensateErr
1✔
344
                        }
1✔
345
                        return errors.Wrap(tenantErr, "tenant data out of sync")
1✔
346
                }
347
                return store.ErrDuplicateEmail
1✔
348
        }
349

350
        if err := ua.db.CreateUser(ctx, u); err != nil {
4✔
351
                if err == store.ErrDuplicateEmail {
2✔
352
                        return err
1✔
353
                }
1✔
354
                if ua.verifyTenant && propagate {
2✔
355
                        // if the user could not be created in the useradm database
1✔
356
                        // try to remove the user from tenantadm
1✔
357
                        if compensateErr := ua.compensateTenantUser(
1✔
358
                                ctx,
1✔
359
                                u.ID,
1✔
360
                                id.Tenant,
1✔
361
                        ); compensateErr != nil {
2✔
362
                                err = errors.Wrap(err, compensateErr.Error())
1✔
363
                        }
1✔
364
                }
365

366
                return errors.Wrap(err, "useradm: failed to create user in the db")
1✔
367
        }
368

369
        return nil
3✔
370
}
371

372
func (ua *UserAdm) compensateTenantUser(ctx context.Context, userId, tenantId string) error {
1✔
373
        err := ua.cTenant.DeleteUser(ctx, tenantId, userId, ua.clientGetter())
1✔
374

1✔
375
        if err != nil {
2✔
376
                return errors.Wrap(err, "faield to delete tenant user")
1✔
377
        }
1✔
378
        return nil
1✔
379
}
380

381
func (ua *UserAdm) validateUserUpdate(
382
        ctx context.Context,
383
        user *model.User,
384
        u *model.UserUpdate,
385
        me bool,
386
) error {
1✔
387
        // user can change own password only
1✔
388
        if !me {
2✔
389
                if len(u.Password) > 0 {
2✔
390
                        return ErrCannotModifyPassword
1✔
391
                }
1✔
392
        } else {
1✔
393
                // when changing own password or email address
1✔
394
                // user has to provide current password
1✔
395
                if len(u.Password) > 0 || (u.Email != "" && u.Email != user.Email) {
2✔
396
                        if err := bcrypt.CompareHashAndPassword(
1✔
397
                                []byte(user.Password),
1✔
398
                                []byte(u.CurrentPassword),
1✔
399
                        ); err != nil {
2✔
400
                                return ErrCurrentPasswordMismatch
1✔
401
                        }
1✔
402
                }
403
        }
404
        return nil
1✔
405
}
406

407
func (ua *UserAdm) deleteAndInvalidateUserTokens(
408
        ctx context.Context,
409
        userID string,
410
        token *jwt.Token,
411
) error {
1✔
412
        var err error
1✔
413
        if token != nil {
2✔
414
                err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, userID, token.ID)
1✔
415
        } else {
2✔
416
                err = ua.db.DeleteTokensByUserId(ctx, userID)
1✔
417
        }
1✔
418
        return err
1✔
419
}
420

421
func (ua *UserAdm) UpdateUser(ctx context.Context, id string, userUpdate *model.UserUpdate) error {
1✔
422
        idty := identity.FromContext(ctx)
1✔
423
        me := idty.Subject == id
1✔
424
        user, err := ua.db.GetUserAndPasswordById(ctx, id)
1✔
425
        if err != nil {
2✔
426
                return errors.Wrap(err, "useradm: failed to get user")
1✔
427
        } else if user == nil {
3✔
428
                return store.ErrUserNotFound
1✔
429
        }
1✔
430

431
        if err := ua.validateUserUpdate(ctx, user, userUpdate, me); err != nil {
2✔
432
                return err
1✔
433
        }
1✔
434

435
        if userUpdate.ETag == nil {
2✔
436
                // Update without the support for etags.
1✔
437
                next := user.NextETag()
1✔
438
                userUpdate.ETagUpdate = &next
1✔
439
                userUpdate.ETag = user.ETag
1✔
440
                if user.ETag == nil {
2✔
441
                        // If the ETag field is not set, assign the etag to nil
1✔
442
                        user.ETag = &model.ETagNil
1✔
443
                }
1✔
444
        } else if user.ETag == nil || *userUpdate.ETag != *user.ETag {
2✔
445
                return ErrETagMismatch
1✔
446
        }
1✔
447

448
        if len(userUpdate.Email) > 0 && userUpdate.Email != user.Email {
2✔
449
                if ua.verifyTenant {
2✔
450
                        err := ua.cTenant.UpdateUser(ctx,
1✔
451
                                idty.Tenant,
1✔
452
                                id,
1✔
453
                                &tenant.UserUpdate{
1✔
454
                                        Name: string(userUpdate.Email),
1✔
455
                                },
1✔
456
                                ua.clientGetter())
1✔
457

1✔
458
                        switch err {
1✔
459
                        case nil:
1✔
460
                                break
1✔
461
                        case tenant.ErrDuplicateUser:
1✔
462
                                return store.ErrDuplicateEmail
1✔
463
                        case tenant.ErrUserNotFound:
1✔
464
                                return store.ErrUserNotFound
1✔
465
                        default:
1✔
466
                                return errors.Wrap(err, "useradm: failed to update user in tenantadm")
1✔
467
                        }
468
                }
469
        }
470

471
        if utils.CheckIfPassSimilarToEmail(user, userUpdate) {
2✔
472
                return ErrPassAndMailTooSimilar
1✔
473
        }
1✔
474

475
        _, err = ua.db.UpdateUser(ctx, id, userUpdate)
1✔
476
        switch err {
1✔
477
        case nil:
1✔
478
                // invalidate the JWT tokens but the one used to update the user
1✔
479
                err = ua.deleteAndInvalidateUserTokens(ctx, id, userUpdate.Token)
1✔
480
                err = errors.Wrap(err, "useradm: failed to invalidate tokens")
1✔
481

482
        case store.ErrUserNotFound:
1✔
483
                // We matched the user earlier, the ETag must have changed in
1✔
484
                // the meantime
1✔
485
                err = ErrETagMismatch
1✔
486
        case store.ErrDuplicateEmail:
1✔
487
                break
1✔
488

489
        default:
1✔
490
                err = errors.Wrap(err, "useradm: failed to update user information")
1✔
491
        }
492

493
        return err
1✔
494
}
495

496
func (ua *UserAdm) Verify(ctx context.Context, token *jwt.Token) error {
1✔
497

1✔
498
        if token == nil {
2✔
499
                return ErrUnauthorized
1✔
500
        }
1✔
501

502
        l := log.FromContext(ctx)
1✔
503

1✔
504
        if !token.Claims.User {
2✔
505
                l.Errorf("not a user token")
1✔
506
                return ErrUnauthorized
1✔
507
        }
1✔
508

509
        if ua.verifyTenant {
2✔
510
                if token.Claims.Tenant == "" {
1✔
511
                        l.Errorf("Token has no tenant claim")
×
512
                        return jwt.ErrTokenInvalid
×
513
                }
×
514
        } else if token.Claims.Tenant != "" {
1✔
515
                l.Errorf("Unexpected tenant claim: %s in the token", token.Claims.Tenant)
×
516
                return jwt.ErrTokenInvalid
×
517
        }
×
518

519
        user, err := ua.db.GetUserById(ctx, token.Claims.Subject.String())
1✔
520
        if user == nil && err == nil {
2✔
521
                return ErrUnauthorized
1✔
522
        }
1✔
523
        if err != nil {
2✔
524
                return errors.Wrap(err, "useradm: failed to get user")
1✔
525
        }
1✔
526

527
        dbToken, err := ua.db.GetTokenById(ctx, token.ID)
1✔
528
        if dbToken == nil && err == nil {
2✔
529
                return ErrUnauthorized
1✔
530
        }
1✔
531
        if err != nil {
2✔
532
                return errors.Wrap(err, "useradm: failed to get token")
1✔
533
        }
1✔
534

535
        // in case the token is a personal access token, update last used timestam
536
        // to not overload the database with writes to tokens collection, we do not
537
        // update the timestamp every time, but instead we wait some configurable
538
        // amount of time between updates
539
        if dbToken.TokenName != nil && ua.config.TokenLastUsedUpdateFreqMinutes > 0 {
1✔
UNCOV
540
                t := time.Now().Add(
×
UNCOV
541
                        (-time.Minute * time.Duration(ua.config.TokenLastUsedUpdateFreqMinutes)))
×
UNCOV
542
                if dbToken.LastUsed == nil || dbToken.LastUsed.Before(t) {
×
UNCOV
543
                        if err := ua.db.UpdateTokenLastUsed(ctx, token.ID); err != nil {
×
544
                                return err
×
545
                        }
×
546
                }
547
        }
548

549
        return nil
1✔
550
}
551

552
func (ua *UserAdm) GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error) {
1✔
553
        users, err := ua.db.GetUsers(ctx, fltr)
1✔
554
        if err != nil {
2✔
555
                return nil, errors.Wrap(err, "useradm: failed to get users")
1✔
556
        }
1✔
557

558
        return users, nil
1✔
559
}
560

561
func (ua *UserAdm) GetUser(ctx context.Context, id string) (*model.User, error) {
1✔
562
        if id == userIdMe {
1✔
UNCOV
563
                id = identity.FromContext(ctx).Subject
×
UNCOV
564
        }
×
565
        user, err := ua.db.GetUserById(ctx, id)
1✔
566
        if err != nil {
2✔
567
                return nil, errors.Wrap(err, "useradm: failed to get user")
1✔
568
        }
1✔
569

570
        return user, nil
1✔
571
}
572

573
func (ua *UserAdm) DeleteUser(ctx context.Context, id string) error {
1✔
574
        if ua.verifyTenant {
2✔
575
                identity := identity.FromContext(ctx)
1✔
576
                err := ua.cTenant.DeleteUser(ctx, identity.Tenant, id, ua.clientGetter())
1✔
577

1✔
578
                if err != nil {
2✔
579
                        return errors.Wrap(err, "useradm: failed to delete user in tenantadm")
1✔
580
                }
1✔
581
        }
582

583
        err := ua.db.DeleteUser(ctx, id)
1✔
584
        if err != nil {
2✔
585
                return errors.Wrap(err, "useradm: failed to delete user")
1✔
586
        }
1✔
587

588
        // remove user tokens
589
        err = ua.db.DeleteTokensByUserId(ctx, id)
1✔
590
        if err != nil {
2✔
591
                return errors.Wrap(err, "useradm: failed to delete user tokens")
1✔
592
        }
1✔
593

594
        return nil
1✔
595
}
596

597
// WithTenantVerification produces a UserAdm instance which enforces
598
// tenant verification vs the tenantadm service upon /login.
599
func (u *UserAdm) WithTenantVerification(c tenant.ClientRunner) *UserAdm {
1✔
600
        u.verifyTenant = true
1✔
601
        u.cTenant = c
1✔
602
        return u
1✔
603
}
1✔
604

605
func (u *UserAdm) CreateTenant(ctx context.Context, tenant model.NewTenant) error {
1✔
606
        return nil
1✔
607
}
1✔
608

609
func (ua *UserAdm) SetPassword(ctx context.Context, uu model.UserUpdate) error {
2✔
610
        u, err := ua.db.GetUserByEmail(ctx, uu.Email)
2✔
611
        if err != nil {
3✔
612
                return errors.Wrap(err, "useradm: failed to get user by email")
1✔
613

1✔
614
        }
1✔
615
        if u == nil {
3✔
616
                return ErrUserNotFound
1✔
617
        }
1✔
618
        if utils.CheckIfPassSimilarToEmail(u, &uu) {
3✔
619
                return ErrPassAndMailTooSimilar
1✔
620
        }
1✔
621

622
        _, err = ua.db.UpdateUser(ctx, u.ID, &uu)
2✔
623

2✔
624
        // if we changed the password, invalidate the JWT tokens but the one used to update the user
2✔
625
        if err == nil && uu.Password != "" {
4✔
626
                if uu.Token != nil {
3✔
627
                        err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, u.ID, uu.Token.ID)
1✔
628
                } else {
3✔
629
                        err = ua.db.DeleteTokensByUserId(ctx, u.ID)
2✔
630
                }
2✔
631
        }
632
        if err != nil {
3✔
633
                return errors.Wrap(err, "useradm: failed to update user information")
1✔
634
        }
1✔
635

636
        return nil
2✔
637
}
638

639
func (ua *UserAdm) DeleteTokens(ctx context.Context, tenantId, userId string) error {
1✔
640
        ctx = identity.WithContext(ctx, &identity.Identity{
1✔
641
                Tenant: tenantId,
1✔
642
        })
1✔
643

1✔
644
        var err error
1✔
645

1✔
646
        if userId != "" {
2✔
647
                err = ua.db.DeleteTokensByUserId(ctx, userId)
1✔
648
        } else {
2✔
649
                err = ua.db.DeleteTokens(ctx)
1✔
650
        }
1✔
651

652
        if err != nil && err != store.ErrTokenNotFound {
2✔
653
                return errors.Wrapf(
1✔
654
                        err,
1✔
655
                        "failed to delete tokens for tenant: %v, user id: %v",
1✔
656
                        tenantId,
1✔
657
                        userId,
1✔
658
                )
1✔
659
        }
1✔
660

661
        return nil
1✔
662
}
663

664
func (u *UserAdm) IssuePersonalAccessToken(
665
        ctx context.Context,
666
        tr *model.TokenRequest,
667
) (string, error) {
1✔
668
        id := identity.FromContext(ctx)
1✔
669
        if id == nil {
1✔
670
                return "", errors.New("identity not present in the context")
×
671
        }
×
672
        if u.config.LimitTokensPerUser > 0 {
2✔
673
                count, err := u.db.CountPersonalAccessTokens(ctx, id.Subject)
1✔
674
                if err != nil {
2✔
675
                        return "", errors.Wrap(err, "useradm: failed to count personal access tokens")
1✔
676
                }
1✔
677
                if count >= int64(u.config.LimitTokensPerUser) {
2✔
678
                        return "", ErrTooManyTokens
1✔
679
                }
1✔
680
        }
681
        //generate and save token
682
        keyId := common.KeyIdFromPath(u.config.PrivateKeyPath, u.config.PrivateKeyFileNamePattern)
1✔
683
        t, err := u.generateToken(
1✔
684
                id.Subject,
1✔
685
                scope.All,
1✔
686
                id.Tenant,
1✔
687
                tr.ExpiresIn == 0,
1✔
688
                keyId,
1✔
689
        )
1✔
690
        if err != nil {
1✔
691
                return "", errors.Wrap(err, "useradm: failed to generate token")
×
692
        }
×
693
        // update claims
694
        t.TokenName = tr.Name
1✔
695
        if tr.ExpiresIn > 0 {
2✔
696
                expires := jwt.Time{
1✔
697
                        Time: time.Now().Add(time.Second * time.Duration(tr.ExpiresIn)),
1✔
698
                }
1✔
699
                t.ExpiresAt = &expires
1✔
700
        }
1✔
701

702
        err = u.db.SaveToken(ctx, t)
1✔
703
        if err == store.ErrDuplicateTokenName {
2✔
704
                return "", ErrDuplicateTokenName
1✔
705
        } else if err != nil {
3✔
706
                return "", errors.Wrap(err, "useradm: failed to save token")
1✔
707
        }
1✔
708

709
        if _, ok := u.jwtHandlers[keyId]; !ok {
1✔
710
                return "", common.ErrKeyIdNotFound
×
711
        }
×
712
        // sign token
713
        return u.jwtHandlers[keyId].ToJWT(t)
1✔
714
}
715

716
func (ua *UserAdm) GetPersonalAccessTokens(
717
        ctx context.Context,
718
        userID string,
UNCOV
719
) ([]model.PersonalAccessToken, error) {
×
UNCOV
720
        tokens, err := ua.db.GetPersonalAccessTokens(ctx, userID)
×
UNCOV
721
        if err != nil {
×
722
                return nil, errors.Wrap(err, "useradm: failed to get tokens")
×
723
        }
×
724

UNCOV
725
        return tokens, nil
×
726
}
727

UNCOV
728
func (ua *UserAdm) DeleteToken(ctx context.Context, id string) error {
×
UNCOV
729
        identity := identity.FromContext(ctx)
×
UNCOV
730
        if identity == nil {
×
731
                return errors.New("identity not present in the context")
×
732
        }
×
UNCOV
733
        err := ua.db.DeleteToken(ctx, oid.FromString(identity.Subject), oid.FromString(id))
×
UNCOV
734
        if err != nil {
×
735
                return errors.Wrap(err, "useradm: failed to delete token")
×
736
        }
×
737

UNCOV
738
        return nil
×
739
}
740

741
func (ua *UserAdm) GetPlans(ctx context.Context, skip, limit int) []model.Plan {
1✔
742
        if (skip + limit) <= len(model.PlanList) {
2✔
743
                return model.PlanList[skip:(skip + limit)]
1✔
744
        } else if skip <= len(model.PlanList) {
3✔
745
                return model.PlanList[skip:]
1✔
746
        }
1✔
747
        return []model.Plan{}
1✔
748
}
749

750
func (ua *UserAdm) GetPlanBinding(ctx context.Context) (*model.PlanBindingDetails, error) {
1✔
751
        if len(model.PlanList) == 1 {
2✔
752
                return &model.PlanBindingDetails{
1✔
753
                        Plan: model.PlanList[0],
1✔
754
                }, nil
1✔
755
        }
1✔
756
        return &model.PlanBindingDetails{}, nil
1✔
757
}
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