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

mendersoftware / useradm / 800583681

pending completion
800583681

Pull #354

gitlab-ci

Fabio Tranchitella
feat: add support for never expiring PATs
Pull Request #354: feat: add support for never expiring PATs

66 of 71 new or added lines in 6 files covered. (92.96%)

117 existing lines in 5 files now uncovered.

2571 of 2875 relevant lines covered (89.43%)

127.6 hits per line

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

91.11
/api/http/api_useradm.go
1
// Copyright 2022 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
package http
15

16
import (
17
        "context"
18
        "net/http"
19
        "strings"
20
        "time"
21

22
        "github.com/ant0ine/go-json-rest/rest"
23
        "github.com/google/uuid"
24
        "github.com/mendersoftware/go-lib-micro/identity"
25
        "github.com/mendersoftware/go-lib-micro/log"
26
        "github.com/mendersoftware/go-lib-micro/rest_utils"
27
        "github.com/mendersoftware/go-lib-micro/routing"
28
        "github.com/pkg/errors"
29

30
        "github.com/mendersoftware/useradm/authz"
31
        "github.com/mendersoftware/useradm/jwt"
32
        "github.com/mendersoftware/useradm/model"
33
        "github.com/mendersoftware/useradm/store"
34
        useradm "github.com/mendersoftware/useradm/user"
35
)
36

37
const (
38
        apiUrlManagementV1      = "/api/management/v1/useradm"
39
        uriManagementAuthLogin  = apiUrlManagementV1 + "/auth/login"
40
        uriManagementAuthLogout = apiUrlManagementV1 + "/auth/logout"
41
        uriManagementUser       = apiUrlManagementV1 + "/users/#id"
42
        uriManagementUsers      = apiUrlManagementV1 + "/users"
43
        uriManagementSettings   = apiUrlManagementV1 + "/settings"
44
        uriManagementSettingsMe = apiUrlManagementV1 + "/settings/me"
45
        uriManagementTokens     = apiUrlManagementV1 + "/settings/tokens"
46
        uriManagementToken      = apiUrlManagementV1 + "/settings/tokens/#id"
47

48
        apiUrlInternalV1  = "/api/internal/v1/useradm"
49
        uriInternalAlive  = apiUrlInternalV1 + "/alive"
50
        uriInternalHealth = apiUrlInternalV1 + "/health"
51

52
        uriInternalAuthVerify  = apiUrlInternalV1 + "/auth/verify"
53
        uriInternalTenants     = apiUrlInternalV1 + "/tenants"
54
        uriInternalTenantUsers = apiUrlInternalV1 + "/tenants/#id/users"
55
        uriInternalTenantUser  = apiUrlInternalV1 + "/tenants/#id/users/#userid"
56
        uriInternalTokens      = apiUrlInternalV1 + "/tokens"
57
)
58

59
const (
60
        defaultTimeout = time.Second * 5
61
        hdrETag        = "ETag"
62
        hdrIfMatch     = "If-Match"
63
)
64

65
const (
66
        uriUIRoot = "/"
67
)
68

69
var (
70
        ErrAuthHeader   = errors.New("invalid or missing auth header")
71
        ErrUserNotFound = errors.New("user not found")
72
)
73

74
type UserAdmApiHandlers struct {
75
        userAdm useradm.App
76
        db      store.DataStore
77
        jwth    *jwt.JWTHandlerRS256
78
        config  Config
79
}
80

81
type Config struct {
82
        // maximum expiration time for Personal Access Token
83
        TokenMaxExpSeconds int
84
}
85

86
// return an ApiHandler for user administration and authentiacation app
87
func NewUserAdmApiHandlers(
88
        userAdm useradm.App,
89
        db store.DataStore,
90
        jwth *jwt.JWTHandlerRS256,
91
        config Config,
92
) ApiHandler {
168✔
93
        return &UserAdmApiHandlers{
168✔
94
                userAdm: userAdm,
168✔
95
                db:      db,
168✔
96
                jwth:    jwth,
168✔
97
                config:  config,
168✔
98
        }
168✔
99
}
168✔
100

101
func (i *UserAdmApiHandlers) GetApp() (rest.App, error) {
168✔
102
        routes := []*rest.Route{
168✔
103
                rest.Get(uriInternalAlive, i.AliveHandler),
168✔
104
                rest.Get(uriInternalHealth, i.HealthHandler),
168✔
105

168✔
106
                rest.Get(uriInternalAuthVerify, i.AuthVerifyHandler),
168✔
107
                rest.Post(uriInternalAuthVerify, i.AuthVerifyHandler),
168✔
108
                rest.Post(uriInternalTenants, i.CreateTenantHandler),
168✔
109
                rest.Post(uriInternalTenantUsers, i.CreateTenantUserHandler),
168✔
110
                rest.Delete(uriInternalTenantUser, i.DeleteTenantUserHandler),
168✔
111
                rest.Get(uriInternalTenantUsers, i.GetTenantUsersHandler),
168✔
112
                rest.Delete(uriInternalTokens, i.DeleteTokensHandler),
168✔
113

168✔
114
                rest.Post(uriManagementAuthLogin, i.AuthLoginHandler),
168✔
115
                rest.Post(uriManagementAuthLogout, i.AuthLogoutHandler),
168✔
116
                rest.Post(uriManagementUsers, i.AddUserHandler),
168✔
117
                rest.Get(uriManagementUsers, i.GetUsersHandler),
168✔
118
                rest.Get(uriManagementUser, i.GetUserHandler),
168✔
119
                rest.Put(uriManagementUser, i.UpdateUserHandler),
168✔
120
                rest.Delete(uriManagementUser, i.DeleteUserHandler),
168✔
121
                rest.Post(uriManagementSettings, i.SaveSettingsHandler),
168✔
122
                rest.Get(uriManagementSettings, i.GetSettingsHandler),
168✔
123
                rest.Post(uriManagementSettingsMe, i.SaveSettingsMeHandler),
168✔
124
                rest.Get(uriManagementSettingsMe, i.GetSettingsMeHandler),
168✔
125
                rest.Post(uriManagementTokens, i.IssueTokenHandler),
168✔
126
                rest.Get(uriManagementTokens, i.GetTokensHandler),
168✔
127
                rest.Delete(uriManagementToken, i.DeleteTokenHandler),
168✔
128
        }
168✔
129

168✔
130
        app, err := rest.MakeRouter(
168✔
131
                // augment routes with OPTIONS handler
168✔
132
                routing.AutogenOptionsRoutes(routes, routing.AllowHeaderOptionsGenerator)...,
168✔
133
        )
168✔
134
        if err != nil {
168✔
135
                return nil, errors.Wrap(err, "failed to create router")
×
136
        }
×
137

138
        return app, nil
168✔
139
}
140

141
func (u *UserAdmApiHandlers) AliveHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
142
        w.WriteHeader(http.StatusNoContent)
2✔
143
}
2✔
144

145
func (u *UserAdmApiHandlers) HealthHandler(w rest.ResponseWriter, r *rest.Request) {
4✔
146
        ctx := r.Context()
4✔
147
        l := log.FromContext(ctx)
4✔
148
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
4✔
149
        defer cancel()
4✔
150

4✔
151
        err := u.userAdm.HealthCheck(ctx)
4✔
152
        if err != nil {
6✔
153
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusServiceUnavailable)
2✔
154
                return
2✔
155
        }
2✔
156
        w.WriteHeader(http.StatusNoContent)
2✔
157
}
158

159
func (u *UserAdmApiHandlers) AuthLoginHandler(w rest.ResponseWriter, r *rest.Request) {
152✔
160
        ctx := r.Context()
152✔
161

152✔
162
        l := log.FromContext(ctx)
152✔
163

152✔
164
        //parse auth header
152✔
165
        user, pass, ok := r.BasicAuth()
152✔
166
        if !ok {
154✔
167
                rest_utils.RestErrWithLog(w, r, l,
2✔
168
                        ErrAuthHeader, http.StatusUnauthorized)
2✔
169
                return
2✔
170
        }
2✔
171
        email := model.Email(strings.ToLower(user))
150✔
172

150✔
173
        token, err := u.userAdm.Login(ctx, email, pass)
150✔
174
        if err != nil {
168✔
175
                switch {
18✔
176
                case err == useradm.ErrUnauthorized || err == useradm.ErrTenantAccountSuspended:
14✔
177
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnauthorized)
14✔
178
                default:
4✔
179
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
4✔
180
                }
181
                return
18✔
182
        }
183

184
        raw, err := u.userAdm.SignToken(ctx, token)
132✔
185
        if err != nil {
134✔
186
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
187
                return
2✔
188
        }
2✔
189

190
        writer := w.(http.ResponseWriter)
130✔
191
        writer.Header().Set("Content-Type", "application/jwt")
130✔
192
        http.SetCookie(writer, &http.Cookie{
130✔
193
                Name:     "JWT",
130✔
194
                Value:    raw,
130✔
195
                Path:     uriUIRoot,
130✔
196
                SameSite: http.SameSiteStrictMode,
130✔
197
                Secure:   true,
130✔
198
                Expires:  token.ExpiresAt.Time,
130✔
199
        })
130✔
200
        _, _ = writer.Write([]byte(raw))
130✔
201
}
202

203
func (u *UserAdmApiHandlers) AuthLogoutHandler(w rest.ResponseWriter, r *rest.Request) {
6✔
204
        ctx := r.Context()
6✔
205
        l := log.FromContext(ctx)
6✔
206

6✔
207
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
12✔
208
                token, err := u.jwth.FromJWT(tokenStr)
6✔
209
                if err != nil {
7✔
210
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
211
                        return
1✔
212
                }
1✔
213
                if err := u.userAdm.Logout(ctx, token); err != nil {
7✔
214
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
215
                        return
2✔
216
                }
2✔
217
        }
218

219
        w.WriteHeader(http.StatusAccepted)
3✔
220
}
221

222
func (u *UserAdmApiHandlers) AuthVerifyHandler(w rest.ResponseWriter, r *rest.Request) {
134✔
223
        ctx := r.Context()
134✔
224

134✔
225
        l := log.FromContext(ctx)
134✔
226

134✔
227
        // note that the request has passed through authz - the token is valid
134✔
228
        token := authz.GetRequestToken(r.Env)
134✔
229

134✔
230
        err := u.userAdm.Verify(ctx, token)
134✔
231
        if err != nil {
157✔
232
                if err == useradm.ErrUnauthorized {
40✔
233
                        rest_utils.RestErrWithLog(w, r, l, useradm.ErrUnauthorized, http.StatusUnauthorized)
17✔
234
                } else {
23✔
235
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
6✔
236
                }
6✔
237
                return
23✔
238
        }
239

240
        w.WriteHeader(http.StatusOK)
111✔
241
}
242

243
func (u *UserAdmApiHandlers) CreateTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
29✔
244
        ctx := r.Context()
29✔
245

29✔
246
        l := log.FromContext(ctx)
29✔
247

29✔
248
        user, err := parseUserInternal(r)
29✔
249
        if err != nil {
43✔
250
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
14✔
251
                return
14✔
252
        }
14✔
253

254
        tenantId := r.PathParam("id")
15✔
255
        if tenantId == "" {
17✔
256
                rest_utils.RestErrWithLog(w, r, l, errors.New("Entity not found"), http.StatusNotFound)
2✔
257
                return
2✔
258
        }
2✔
259
        ctx = getTenantContext(ctx, tenantId)
13✔
260
        err = u.userAdm.CreateUserInternal(ctx, user)
13✔
261
        if err != nil {
15✔
262
                if err == store.ErrDuplicateEmail {
4✔
263
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
2✔
264
                } else {
2✔
UNCOV
265
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
266
                }
×
267
                return
2✔
268
        }
269

270
        w.WriteHeader(http.StatusCreated)
11✔
271

272
}
273

274
func (u *UserAdmApiHandlers) AddUserHandler(w rest.ResponseWriter, r *rest.Request) {
23✔
275
        ctx := r.Context()
23✔
276

23✔
277
        l := log.FromContext(ctx)
23✔
278

23✔
279
        user, err := parseUser(r)
23✔
280
        if err != nil {
35✔
281
                if err == model.ErrPasswordTooShort {
16✔
282
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
4✔
283
                } else {
12✔
284
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
8✔
285
                }
8✔
286
                return
12✔
287
        }
288

289
        err = u.userAdm.CreateUser(ctx, user)
11✔
290
        if err != nil {
15✔
291
                if err == store.ErrDuplicateEmail {
8✔
292
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
4✔
293
                } else {
4✔
UNCOV
294
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
295
                }
×
296
                return
4✔
297
        }
298

299
        w.Header().Add("Location", "users/"+string(user.ID))
7✔
300
        w.WriteHeader(http.StatusCreated)
7✔
301

302
}
303

304
func (u *UserAdmApiHandlers) GetUsersHandler(w rest.ResponseWriter, r *rest.Request) {
359✔
305
        ctx := r.Context()
359✔
306

359✔
307
        l := log.FromContext(ctx)
359✔
308

359✔
309
        if err := r.ParseForm(); err != nil {
363✔
310
                err = errors.Wrap(err, "api: bad form parameters")
4✔
311
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
4✔
312
                return
4✔
313
        }
4✔
314

315
        fltr := model.UserFilter{}
355✔
316
        if err := fltr.ParseForm(r.Form); err != nil {
359✔
317
                err = errors.Wrap(err, "api: invalid form values")
4✔
318
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
4✔
319
                return
4✔
320
        }
4✔
321

322
        users, err := u.userAdm.GetUsers(ctx, fltr)
351✔
323
        if err != nil {
355✔
324
                rest_utils.RestErrWithLogInternal(w, r, l, err)
4✔
325
                return
4✔
326
        }
4✔
327

328
        _ = w.WriteJson(users)
347✔
329
}
330

331
func (u *UserAdmApiHandlers) GetTenantUsersHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
332
        ctx := r.Context()
10✔
333
        ctx = identity.WithContext(ctx, &identity.Identity{
10✔
334
                Tenant: r.PathParam("id"),
10✔
335
        })
10✔
336
        r.Request = r.Request.WithContext(ctx)
10✔
337
        u.GetUsersHandler(w, r)
10✔
338
}
10✔
339

340
func (u *UserAdmApiHandlers) GetUserHandler(w rest.ResponseWriter, r *rest.Request) {
36✔
341
        ctx := r.Context()
36✔
342

36✔
343
        l := log.FromContext(ctx)
36✔
344

36✔
345
        id := r.PathParam("id")
36✔
346
        user, err := u.userAdm.GetUser(ctx, id)
36✔
347
        if err != nil {
38✔
348
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
349
                return
2✔
350
        }
2✔
351

352
        if user == nil {
39✔
353
                rest_utils.RestErrWithLog(w, r, l, ErrUserNotFound, 404)
5✔
354
                return
5✔
355
        }
5✔
356

357
        _ = w.WriteJson(user)
29✔
358
}
359

360
func (u *UserAdmApiHandlers) UpdateUserHandler(w rest.ResponseWriter, r *rest.Request) {
42✔
361
        ctx := r.Context()
42✔
362
        idty := identity.FromContext(ctx)
42✔
363
        l := log.FromContext(ctx)
42✔
364

42✔
365
        userUpdate, err := parseUserUpdate(r)
42✔
366
        if err != nil {
51✔
367
                if err == model.ErrPasswordTooShort {
11✔
368
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
2✔
369
                } else {
9✔
370
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
7✔
371
                }
7✔
372
                return
9✔
373
        }
374

375
        // extract the token used to update the user
376
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
58✔
377
                token, err := u.jwth.FromJWT(tokenStr)
25✔
378
                if err != nil {
25✔
UNCOV
379
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
380
                        return
×
UNCOV
381
                }
×
382
                userUpdate.Token = token
25✔
383
        }
384

385
        id := r.PathParam("id")
33✔
386
        if strings.EqualFold(id, "me") {
46✔
387
                id = idty.Subject
13✔
388
        }
13✔
389
        err = u.userAdm.UpdateUser(ctx, id, userUpdate)
33✔
390
        if err != nil {
47✔
391
                switch err {
14✔
392
                case store.ErrDuplicateEmail,
393
                        useradm.ErrCurrentPasswordMismatch,
394
                        useradm.ErrCannotModifyPassword:
9✔
395
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
9✔
396
                case store.ErrUserNotFound:
3✔
397
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
3✔
398
                case useradm.ErrETagMismatch:
2✔
399
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusConflict)
2✔
UNCOV
400
                default:
×
UNCOV
401
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
402
                }
403
        }
404

405
        w.WriteHeader(http.StatusNoContent)
33✔
406
}
407

408
func (u *UserAdmApiHandlers) DeleteTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
6✔
409
        ctx := r.Context()
6✔
410

6✔
411
        tenantId := r.PathParam("id")
6✔
412
        if tenantId != "" {
8✔
413
                ctx = getTenantContext(ctx, tenantId)
2✔
414
        }
2✔
415

416
        l := log.FromContext(ctx)
6✔
417
        err := u.userAdm.DeleteUser(ctx, r.PathParam("userid"))
6✔
418
        if err != nil {
8✔
419
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
420
                return
2✔
421
        }
2✔
422

423
        w.WriteHeader(http.StatusNoContent)
4✔
424
}
425

426
func (u *UserAdmApiHandlers) DeleteUserHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
427
        ctx := r.Context()
10✔
428

10✔
429
        l := log.FromContext(ctx)
10✔
430

10✔
431
        err := u.userAdm.DeleteUser(ctx, r.PathParam("id"))
10✔
432
        if err != nil {
12✔
433
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
434
                return
2✔
435
        }
2✔
436

437
        w.WriteHeader(http.StatusNoContent)
8✔
438
}
439

440
func parseUser(r *rest.Request) (*model.User, error) {
23✔
441
        user := model.User{}
23✔
442

23✔
443
        //decode body
23✔
444
        err := r.DecodeJsonPayload(&user)
23✔
445
        if err != nil {
25✔
446
                return nil, errors.Wrap(err, "failed to decode request body")
2✔
447
        }
2✔
448

449
        if err := user.Validate(); err != nil {
31✔
450
                return nil, err
10✔
451
        }
10✔
452

453
        return &user, nil
11✔
454
}
455

456
func parseUserInternal(r *rest.Request) (*model.UserInternal, error) {
29✔
457
        user := model.UserInternal{}
29✔
458

29✔
459
        //decode body
29✔
460
        err := r.DecodeJsonPayload(&user)
29✔
461
        if err != nil {
31✔
462
                return nil, errors.Wrap(err, "failed to decode request body")
2✔
463
        }
2✔
464

465
        if err := user.Validate(); err != nil {
39✔
466
                return nil, err
12✔
467
        }
12✔
468

469
        return &user, nil
15✔
470
}
471

472
func parseUserUpdate(r *rest.Request) (*model.UserUpdate, error) {
42✔
473
        userUpdate := model.UserUpdate{}
42✔
474

42✔
475
        //decode body
42✔
476
        err := r.DecodeJsonPayload(&userUpdate)
42✔
477
        if err != nil {
44✔
478
                return nil, errors.Wrap(err, "failed to decode request body")
2✔
479
        }
2✔
480

481
        if err := userUpdate.Validate(); err != nil {
47✔
482
                return nil, err
7✔
483
        }
7✔
484

485
        return &userUpdate, nil
33✔
486
}
487

488
func (u *UserAdmApiHandlers) CreateTenantHandler(w rest.ResponseWriter, r *rest.Request) {
12✔
489
        ctx := r.Context()
12✔
490

12✔
491
        l := log.FromContext(ctx)
12✔
492

12✔
493
        var newTenant model.NewTenant
12✔
494

12✔
495
        if err := r.DecodeJsonPayload(&newTenant); err != nil {
14✔
496
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
497
                return
2✔
498
        }
2✔
499

500
        if err := newTenant.Validate(); err != nil {
13✔
501
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
3✔
502
                return
3✔
503
        }
3✔
504

505
        err := u.userAdm.CreateTenant(ctx, model.NewTenant{
7✔
506
                ID: newTenant.ID,
7✔
507
        })
7✔
508
        if err != nil {
9✔
509
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
510
                return
2✔
511
        }
2✔
512

513
        w.WriteHeader(http.StatusCreated)
5✔
514
}
515

516
func getTenantContext(ctx context.Context, tenantId string) context.Context {
15✔
517
        if ctx == nil {
15✔
UNCOV
518
                ctx = context.Background()
×
UNCOV
519
        }
×
520
        if tenantId != "" {
30✔
521
                id := &identity.Identity{
15✔
522
                        Tenant: tenantId,
15✔
523
                }
15✔
524

15✔
525
                ctx = identity.WithContext(ctx, id)
15✔
526
        }
15✔
527

528
        return ctx
15✔
529
}
530

531
func (u *UserAdmApiHandlers) DeleteTokensHandler(w rest.ResponseWriter, r *rest.Request) {
14✔
532

14✔
533
        ctx := r.Context()
14✔
534

14✔
535
        l := log.FromContext(ctx)
14✔
536

14✔
537
        tenantId := r.URL.Query().Get("tenant_id")
14✔
538
        if tenantId == "" {
18✔
539
                rest_utils.RestErrWithLog(
4✔
540
                        w,
4✔
541
                        r,
4✔
542
                        l,
4✔
543
                        errors.New("tenant_id must be provided"),
4✔
544
                        http.StatusBadRequest,
4✔
545
                )
4✔
546
                return
4✔
547
        }
4✔
548
        userId := r.URL.Query().Get("user_id")
10✔
549

10✔
550
        err := u.userAdm.DeleteTokens(ctx, tenantId, userId)
10✔
551
        switch err {
10✔
552
        case nil:
8✔
553
                w.WriteHeader(http.StatusNoContent)
8✔
554
        default:
2✔
555
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
556
        }
557
}
558

559
func (u *UserAdmApiHandlers) SaveSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
21✔
560
        u.saveSettingsHandler(w, r, false)
21✔
561
}
21✔
562

UNCOV
563
func (u *UserAdmApiHandlers) SaveSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
564
        u.saveSettingsHandler(w, r, true)
×
UNCOV
565
}
×
566

567
func (u *UserAdmApiHandlers) saveSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
21✔
568
        ctx := r.Context()
21✔
569

21✔
570
        l := log.FromContext(ctx)
21✔
571

21✔
572
        settings := &model.Settings{}
21✔
573
        err := r.DecodeJsonPayload(settings)
21✔
574
        if err != nil {
26✔
575
                rest_utils.RestErrWithLog(
5✔
576
                        w,
5✔
577
                        r,
5✔
578
                        l,
5✔
579
                        errors.New("cannot parse request body as json"),
5✔
580
                        http.StatusBadRequest,
5✔
581
                )
5✔
582
                return
5✔
583
        }
5✔
584

585
        if err := settings.Validate(); err != nil {
18✔
586
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
587
                return
2✔
588
        }
2✔
589

590
        ifMatchHeader := r.Header.Get(hdrIfMatch)
14✔
591

14✔
592
        settings.ETag = uuid.NewString()
14✔
593
        if me {
14✔
UNCOV
594
                id := identity.FromContext(ctx)
×
UNCOV
595
                if id == nil {
×
UNCOV
596
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
UNCOV
597
                        return
×
UNCOV
598
                }
×
UNCOV
599
                err = u.db.SaveUserSettings(ctx, id.Subject, settings, ifMatchHeader)
×
600
        } else {
14✔
601
                err = u.db.SaveSettings(ctx, settings, ifMatchHeader)
14✔
602
        }
14✔
603
        if err == store.ErrETagMismatch {
16✔
604
                rest_utils.RestErrWithInfoMsg(w, r, l, err, http.StatusPreconditionFailed, err.Error())
2✔
605
                return
2✔
606
        } else if err != nil {
16✔
607
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
608
                return
2✔
609
        }
2✔
610

611
        w.WriteHeader(http.StatusCreated)
10✔
612
}
613

614
func (u *UserAdmApiHandlers) GetSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
15✔
615
        u.getSettingsHandler(w, r, false)
15✔
616
}
15✔
617

UNCOV
618
func (u *UserAdmApiHandlers) GetSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
619
        u.getSettingsHandler(w, r, true)
×
UNCOV
620
}
×
621

622
func (u *UserAdmApiHandlers) getSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
15✔
623
        ctx := r.Context()
15✔
624

15✔
625
        l := log.FromContext(ctx)
15✔
626

15✔
627
        var settings *model.Settings
15✔
628
        var err error
15✔
629
        if me {
15✔
630
                id := identity.FromContext(ctx)
×
UNCOV
631
                if id == nil {
×
UNCOV
632
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
UNCOV
633
                        return
×
UNCOV
634
                }
×
UNCOV
635
                settings, err = u.db.GetUserSettings(ctx, id.Subject)
×
636
        } else {
15✔
637
                settings, err = u.db.GetSettings(ctx)
15✔
638
        }
15✔
639

640
        if err != nil {
17✔
641
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
642
                return
2✔
643
        } else if settings == nil {
17✔
644
                settings = &model.Settings{
2✔
645
                        Values: model.SettingsValues{},
2✔
646
                }
2✔
647
        }
2✔
648

649
        if settings.ETag != "" {
22✔
650
                w.Header().Set(hdrETag, settings.ETag)
9✔
651
        }
9✔
652
        _ = w.WriteJson(settings)
13✔
653
}
654

655
func (u *UserAdmApiHandlers) IssueTokenHandler(w rest.ResponseWriter, r *rest.Request) {
63✔
656
        ctx := r.Context()
63✔
657

63✔
658
        l := log.FromContext(ctx)
63✔
659

63✔
660
        var tokenRequest model.TokenRequest
63✔
661

63✔
662
        if err := r.DecodeJsonPayload(&tokenRequest); err != nil {
63✔
UNCOV
663
                rest_utils.RestErrWithLog(
×
UNCOV
664
                        w,
×
UNCOV
665
                        r,
×
UNCOV
666
                        l,
×
UNCOV
667
                        errors.New("cannot parse request body as json"),
×
UNCOV
668
                        http.StatusBadRequest,
×
UNCOV
669
                )
×
UNCOV
670
                return
×
UNCOV
671
        }
×
672
        if err := tokenRequest.Validate(u.config.TokenMaxExpSeconds); err != nil {
67✔
673
                rest_utils.RestErrWithLog(
4✔
674
                        w,
4✔
675
                        r,
4✔
676
                        l,
4✔
677
                        err,
4✔
678
                        http.StatusBadRequest,
4✔
679
                )
4✔
680
                return
4✔
681
        }
4✔
682

683
        token, err := u.userAdm.IssuePersonalAccessToken(ctx, &tokenRequest)
59✔
684
        switch err {
59✔
685
        case nil:
46✔
686
                writer := w.(http.ResponseWriter)
46✔
687
                writer.Header().Set("Content-Type", "application/jwt")
46✔
688
                _, _ = writer.Write([]byte(token))
46✔
689
        case useradm.ErrTooManyTokens:
5✔
690
                rest_utils.RestErrWithLog(
5✔
691
                        w,
5✔
692
                        r,
5✔
693
                        l,
5✔
694
                        err,
5✔
695
                        http.StatusUnprocessableEntity,
5✔
696
                )
5✔
697
        case useradm.ErrDuplicateTokenName:
8✔
698
                rest_utils.RestErrWithLog(
8✔
699
                        w,
8✔
700
                        r,
8✔
701
                        l,
8✔
702
                        err,
8✔
703
                        http.StatusConflict,
8✔
704
                )
8✔
UNCOV
705
        default:
×
UNCOV
706
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
707
        }
708
}
709

710
func (u *UserAdmApiHandlers) GetTokensHandler(w rest.ResponseWriter, r *rest.Request) {
10✔
711
        ctx := r.Context()
10✔
712
        l := log.FromContext(ctx)
10✔
713
        id := identity.FromContext(ctx)
10✔
714
        if id == nil {
10✔
715
                rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
716
                return
×
UNCOV
717
        }
×
718

719
        tokens, err := u.userAdm.GetPersonalAccessTokens(ctx, id.Subject)
10✔
720
        if err != nil {
12✔
721
                rest_utils.RestErrWithLogInternal(w, r, l, err)
2✔
722
                return
2✔
723
        }
2✔
724

725
        _ = w.WriteJson(tokens)
8✔
726
}
727

728
func (u *UserAdmApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
729
        ctx := r.Context()
3✔
730

3✔
731
        l := log.FromContext(ctx)
3✔
732

3✔
733
        err := u.userAdm.DeleteToken(ctx, r.PathParam("id"))
3✔
734
        if err != nil {
3✔
UNCOV
735
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
736
                return
×
UNCOV
737
        }
×
738

739
        w.WriteHeader(http.StatusNoContent)
3✔
740
}
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