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

mendersoftware / mender-server / 1495380963

14 Oct 2024 03:35PM UTC coverage: 70.373% (-2.5%) from 72.904%
1495380963

Pull #101

gitlab-ci

mineralsfree
feat: tenant list added

Ticket: MEN-7568
Changelog: None

Signed-off-by: Mikita Pilinka <mikita.pilinka@northern.tech>
Pull Request #101: feat: tenant list added

4406 of 6391 branches covered (68.94%)

Branch coverage included in aggregate %.

88 of 183 new or added lines in 10 files covered. (48.09%)

2623 existing lines in 65 files now uncovered.

36673 of 51982 relevant lines covered (70.55%)

31.07 hits per line

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

87.37
/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
)
36

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

56
const (
57
        TenantStatusSuspended = "suspended"
58
        userIdMe              = "me"
59
)
60

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

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

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

87
        DeleteTokens(ctx context.Context, tenantId, userId string) error
88

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

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

115
type ApiClientGetter func() apiclient.HttpRunner
116

117
func simpleApiClientGetter() apiclient.HttpRunner {
1✔
118
        return &apiclient.HttpApi{}
1✔
119
}
1✔
120

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

131
func NewUserAdm(jwtHandlers map[int]jwt.Handler, db store.DataStore, config Config) *UserAdm {
1✔
132

1✔
133
        return &UserAdm{
1✔
134
                jwtHandlers:  jwtHandlers,
1✔
135
                db:           db,
1✔
136
                config:       config,
1✔
137
                clientGetter: simpleApiClientGetter,
1✔
138
        }
1✔
139
}
1✔
140

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

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

154
        return nil
1✔
155
}
156

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

1✔
162
        if email == "" {
1✔
163
                return nil, ErrUnauthorized
×
164
        }
×
165

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

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

174
                if tenant == nil {
2✔
175
                        return nil, ErrUnauthorized
1✔
176
                }
1✔
177

178
                if tenant.Status == TenantStatusSuspended {
2✔
179
                        return nil, ErrTenantAccountSuspended
1✔
180
                }
1✔
181

182
                ident.Tenant = tenant.ID
1✔
183
                ctx = identity.WithContext(ctx, &ident)
1✔
184
        }
185

186
        //get user
187
        user, err := u.db.GetUserByEmail(ctx, email)
1✔
188

1✔
189
        if user == nil && err == nil {
2✔
190
                return nil, ErrUnauthorized
1✔
191
        }
1✔
192

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

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

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

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

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

230
        return t, nil
1✔
231
}
232

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

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

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

UNCOV
269
func (ua *UserAdm) CreateUser(ctx context.Context, u *model.User) error {
×
UNCOV
270
        hash, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
×
UNCOV
271
        if err != nil {
×
272
                return errors.Wrap(err, "failed to generate password hash")
×
273
        }
×
UNCOV
274
        u.Password = string(hash)
×
UNCOV
275

×
UNCOV
276
        return ua.doCreateUser(ctx, u, true)
×
277
}
278

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

290
        return ua.doCreateUser(ctx, &u.User, u.ShouldPropagate())
×
291
}
292

293
func (ua *UserAdm) doCreateUser(ctx context.Context, u *model.User, propagate bool) error {
1✔
294
        var tenantErr error
1✔
295

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

309
        id := identity.FromContext(ctx)
1✔
310
        if ua.verifyTenant && propagate {
2✔
311
                tenantErr = ua.cTenant.CreateUser(ctx,
1✔
312
                        &tenant.User{
1✔
313
                                ID:       u.ID,
1✔
314
                                Name:     string(u.Email),
1✔
315
                                TenantID: id.Tenant,
1✔
316
                        },
1✔
317
                        ua.clientGetter())
1✔
318

1✔
319
                if tenantErr != nil && tenantErr != tenant.ErrDuplicateUser {
2✔
320
                        return errors.Wrap(tenantErr, "useradm: failed to create user in tenantadm")
1✔
321
                }
1✔
322
        }
323

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

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

360
                return errors.Wrap(err, "useradm: failed to create user in the db")
1✔
361
        }
362

363
        return nil
1✔
364
}
365

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

1✔
369
        if err != nil {
2✔
370
                return errors.Wrap(err, "faield to delete tenant user")
1✔
371
        }
1✔
372
        return nil
1✔
373
}
374

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

401
func (ua *UserAdm) deleteAndInvalidateUserTokens(
402
        ctx context.Context,
403
        userID string,
404
        token *jwt.Token,
405
) error {
1✔
406
        var err error
1✔
407
        if token != nil {
2✔
408
                err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, userID, token.ID)
1✔
409
        } else {
2✔
410
                err = ua.db.DeleteTokensByUserId(ctx, userID)
1✔
411
        }
1✔
412
        return err
1✔
413
}
414

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

425
        if err := ua.validateUserUpdate(ctx, user, userUpdate, me); err != nil {
2✔
426
                return err
1✔
427
        }
1✔
428

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

442
        if len(userUpdate.Email) > 0 && userUpdate.Email != user.Email {
2✔
443
                if ua.verifyTenant {
2✔
444
                        err := ua.cTenant.UpdateUser(ctx,
1✔
445
                                idty.Tenant,
1✔
446
                                id,
1✔
447
                                &tenant.UserUpdate{
1✔
448
                                        Name: string(userUpdate.Email),
1✔
449
                                },
1✔
450
                                ua.clientGetter())
1✔
451

1✔
452
                        switch err {
1✔
453
                        case nil:
1✔
454
                                break
1✔
455
                        case tenant.ErrDuplicateUser:
1✔
456
                                return store.ErrDuplicateEmail
1✔
457
                        case tenant.ErrUserNotFound:
1✔
458
                                return store.ErrUserNotFound
1✔
459
                        default:
1✔
460
                                return errors.Wrap(err, "useradm: failed to update user in tenantadm")
1✔
461
                        }
462
                }
463
        }
464

465
        _, err = ua.db.UpdateUser(ctx, id, userUpdate)
1✔
466
        switch err {
1✔
467
        case nil:
1✔
468
                // invalidate the JWT tokens but the one used to update the user
1✔
469
                err = ua.deleteAndInvalidateUserTokens(ctx, id, userUpdate.Token)
1✔
470
                err = errors.Wrap(err, "useradm: failed to invalidate tokens")
1✔
471

472
        case store.ErrUserNotFound:
1✔
473
                // We matched the user earlier, the ETag must have changed in
1✔
474
                // the meantime
1✔
475
                err = ErrETagMismatch
1✔
476
        case store.ErrDuplicateEmail:
1✔
477
                break
1✔
478

479
        default:
1✔
480
                err = errors.Wrap(err, "useradm: failed to update user information")
1✔
481
        }
482

483
        return err
1✔
484
}
485

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

1✔
488
        if token == nil {
2✔
489
                return ErrUnauthorized
1✔
490
        }
1✔
491

492
        l := log.FromContext(ctx)
1✔
493

1✔
494
        if !token.Claims.User {
2✔
495
                l.Errorf("not a user token")
1✔
496
                return ErrUnauthorized
1✔
497
        }
1✔
498

499
        if ua.verifyTenant {
2✔
500
                if token.Claims.Tenant == "" {
1✔
501
                        l.Errorf("Token has no tenant claim")
×
502
                        return jwt.ErrTokenInvalid
×
503
                }
×
504
        } else if token.Claims.Tenant != "" {
1✔
505
                l.Errorf("Unexpected tenant claim: %s in the token", token.Claims.Tenant)
×
506
                return jwt.ErrTokenInvalid
×
507
        }
×
508

509
        user, err := ua.db.GetUserById(ctx, token.Claims.Subject.String())
1✔
510
        if user == nil && err == nil {
2✔
511
                return ErrUnauthorized
1✔
512
        }
1✔
513
        if err != nil {
2✔
514
                return errors.Wrap(err, "useradm: failed to get user")
1✔
515
        }
1✔
516

517
        dbToken, err := ua.db.GetTokenById(ctx, token.ID)
1✔
518
        if dbToken == nil && err == nil {
2✔
519
                return ErrUnauthorized
1✔
520
        }
1✔
521
        if err != nil {
2✔
522
                return errors.Wrap(err, "useradm: failed to get token")
1✔
523
        }
1✔
524

525
        // in case the token is a personal access token, update last used timestam
526
        // to not overload the database with writes to tokens collection, we do not
527
        // update the timestamp every time, but instead we wait some configurable
528
        // amount of time between updates
529
        if dbToken.TokenName != nil && ua.config.TokenLastUsedUpdateFreqMinutes > 0 {
1✔
530
                t := time.Now().Add(
×
531
                        (-time.Minute * time.Duration(ua.config.TokenLastUsedUpdateFreqMinutes)))
×
532
                if dbToken.LastUsed == nil || dbToken.LastUsed.Before(t) {
×
533
                        if err := ua.db.UpdateTokenLastUsed(ctx, token.ID); err != nil {
×
534
                                return err
×
535
                        }
×
536
                }
537
        }
538

539
        return nil
1✔
540
}
541

542
func (ua *UserAdm) GetUsers(ctx context.Context, fltr model.UserFilter) ([]model.User, error) {
1✔
543
        users, err := ua.db.GetUsers(ctx, fltr)
1✔
544
        if err != nil {
2✔
545
                return nil, errors.Wrap(err, "useradm: failed to get users")
1✔
546
        }
1✔
547

548
        return users, nil
1✔
549
}
550

551
func (ua *UserAdm) GetUser(ctx context.Context, id string) (*model.User, error) {
1✔
552
        if id == userIdMe {
1✔
553
                id = identity.FromContext(ctx).Subject
×
554
        }
×
555
        user, err := ua.db.GetUserById(ctx, id)
1✔
556
        if err != nil {
2✔
557
                return nil, errors.Wrap(err, "useradm: failed to get user")
1✔
558
        }
1✔
559

560
        return user, nil
1✔
561
}
562

563
func (ua *UserAdm) DeleteUser(ctx context.Context, id string) error {
1✔
564
        if ua.verifyTenant {
2✔
565
                identity := identity.FromContext(ctx)
1✔
566
                err := ua.cTenant.DeleteUser(ctx, identity.Tenant, id, ua.clientGetter())
1✔
567

1✔
568
                if err != nil {
2✔
569
                        return errors.Wrap(err, "useradm: failed to delete user in tenantadm")
1✔
570
                }
1✔
571
        }
572

573
        err := ua.db.DeleteUser(ctx, id)
1✔
574
        if err != nil {
2✔
575
                return errors.Wrap(err, "useradm: failed to delete user")
1✔
576
        }
1✔
577

578
        // remove user tokens
579
        err = ua.db.DeleteTokensByUserId(ctx, id)
1✔
580
        if err != nil {
2✔
581
                return errors.Wrap(err, "useradm: failed to delete user tokens")
1✔
582
        }
1✔
583

584
        return nil
1✔
585
}
586

587
// WithTenantVerification produces a UserAdm instance which enforces
588
// tenant verification vs the tenantadm service upon /login.
589
func (u *UserAdm) WithTenantVerification(c tenant.ClientRunner) *UserAdm {
1✔
590
        u.verifyTenant = true
1✔
591
        u.cTenant = c
1✔
592
        return u
1✔
593
}
1✔
594

595
func (u *UserAdm) CreateTenant(ctx context.Context, tenant model.NewTenant) error {
1✔
596
        return nil
1✔
597
}
1✔
598

599
func (ua *UserAdm) SetPassword(ctx context.Context, uu model.UserUpdate) error {
1✔
600
        u, err := ua.db.GetUserByEmail(ctx, uu.Email)
1✔
601
        if err != nil {
2✔
602
                return errors.Wrap(err, "useradm: failed to get user by email")
1✔
603

1✔
604
        }
1✔
605
        if u == nil {
2✔
606
                return ErrUserNotFound
1✔
607
        }
1✔
608

609
        _, err = ua.db.UpdateUser(ctx, u.ID, &uu)
1✔
610

1✔
611
        // if we changed the password, invalidate the JWT tokens but the one used to update the user
1✔
612
        if err == nil && uu.Password != "" {
2✔
613
                if uu.Token != nil {
2✔
614
                        err = ua.db.DeleteTokensByUserIdExceptCurrentOne(ctx, u.ID, uu.Token.ID)
1✔
615
                } else {
2✔
616
                        err = ua.db.DeleteTokensByUserId(ctx, u.ID)
1✔
617
                }
1✔
618
        }
619
        if err != nil {
2✔
620
                return errors.Wrap(err, "useradm: failed to update user information")
1✔
621
        }
1✔
622

623
        return nil
1✔
624
}
625

626
func (ua *UserAdm) DeleteTokens(ctx context.Context, tenantId, userId string) error {
1✔
627
        ctx = identity.WithContext(ctx, &identity.Identity{
1✔
628
                Tenant: tenantId,
1✔
629
        })
1✔
630

1✔
631
        var err error
1✔
632

1✔
633
        if userId != "" {
2✔
634
                err = ua.db.DeleteTokensByUserId(ctx, userId)
1✔
635
        } else {
2✔
636
                err = ua.db.DeleteTokens(ctx)
1✔
637
        }
1✔
638

639
        if err != nil && err != store.ErrTokenNotFound {
2✔
640
                return errors.Wrapf(
1✔
641
                        err,
1✔
642
                        "failed to delete tokens for tenant: %v, user id: %v",
1✔
643
                        tenantId,
1✔
644
                        userId,
1✔
645
                )
1✔
646
        }
1✔
647

648
        return nil
1✔
649
}
650

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

689
        err = u.db.SaveToken(ctx, t)
1✔
690
        if err == store.ErrDuplicateTokenName {
2✔
691
                return "", ErrDuplicateTokenName
1✔
692
        } else if err != nil {
3✔
693
                return "", errors.Wrap(err, "useradm: failed to save token")
1✔
694
        }
1✔
695

696
        if _, ok := u.jwtHandlers[keyId]; !ok {
1✔
697
                return "", common.ErrKeyIdNotFound
×
698
        }
×
699
        // sign token
700
        return u.jwtHandlers[keyId].ToJWT(t)
1✔
701
}
702

703
func (ua *UserAdm) GetPersonalAccessTokens(
704
        ctx context.Context,
705
        userID string,
706
) ([]model.PersonalAccessToken, error) {
×
707
        tokens, err := ua.db.GetPersonalAccessTokens(ctx, userID)
×
708
        if err != nil {
×
709
                return nil, errors.Wrap(err, "useradm: failed to get tokens")
×
710
        }
×
711

712
        return tokens, nil
×
713
}
714

715
func (ua *UserAdm) DeleteToken(ctx context.Context, id string) error {
×
716
        identity := identity.FromContext(ctx)
×
717
        if identity == nil {
×
718
                return errors.New("identity not present in the context")
×
719
        }
×
720
        err := ua.db.DeleteToken(ctx, oid.FromString(identity.Subject), oid.FromString(id))
×
721
        if err != nil {
×
722
                return errors.Wrap(err, "useradm: failed to delete token")
×
723
        }
×
724

725
        return nil
×
726
}
727

728
func (ua *UserAdm) GetPlans(ctx context.Context, skip, limit int) []model.Plan {
1✔
729
        if (skip + limit) <= len(model.PlanList) {
2✔
730
                return model.PlanList[skip:(skip + limit)]
1✔
731
        } else if skip <= len(model.PlanList) {
3✔
732
                return model.PlanList[skip:]
1✔
733
        }
1✔
734
        return []model.Plan{}
1✔
735
}
736

737
func (ua *UserAdm) GetPlanBinding(ctx context.Context) (*model.PlanBindingDetails, error) {
1✔
738
        if len(model.PlanList) == 1 {
2✔
739
                return &model.PlanBindingDetails{
1✔
740
                        Plan: model.PlanList[0],
1✔
741
                }, nil
1✔
742
        }
1✔
743
        return &model.PlanBindingDetails{}, nil
1✔
744
}
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