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

mendersoftware / mender-server / 1915348700

09 Jul 2025 07:07AM UTC coverage: 65.635% (+0.2%) from 65.484%
1915348700

Pull #781

gitlab-ci

alfrunes
test(pkg): Serialize test execution of packages for pkg/...

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #781: Created interface and implementation of distributed locks

34 of 42 new or added lines in 1 file covered. (80.95%)

75 existing lines in 5 files now uncovered.

32330 of 49257 relevant lines covered (65.64%)

1.39 hits per line

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

91.19
/backend/services/useradm/api/http/api_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
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/pkg/errors"
25

26
        "github.com/mendersoftware/mender-server/pkg/accesslog"
27
        "github.com/mendersoftware/mender-server/pkg/identity"
28
        "github.com/mendersoftware/mender-server/pkg/log"
29
        "github.com/mendersoftware/mender-server/pkg/requestid"
30
        "github.com/mendersoftware/mender-server/pkg/requestlog"
31
        "github.com/mendersoftware/mender-server/pkg/rest_utils"
32
        "github.com/mendersoftware/mender-server/pkg/routing"
33

34
        "github.com/mendersoftware/mender-server/services/useradm/authz"
35
        "github.com/mendersoftware/mender-server/services/useradm/jwt"
36
        "github.com/mendersoftware/mender-server/services/useradm/model"
37
        "github.com/mendersoftware/mender-server/services/useradm/store"
38
        useradm "github.com/mendersoftware/mender-server/services/useradm/user"
39
)
40

41
const (
42
        apiUrlManagementV1       = "/api/management/v1/useradm"
43
        uriManagementAuthLogin   = apiUrlManagementV1 + "/auth/login"
44
        uriManagementAuthLogout  = apiUrlManagementV1 + "/auth/logout"
45
        uriManagementUser        = apiUrlManagementV1 + "/users/#id"
46
        uriManagementUsers       = apiUrlManagementV1 + "/users"
47
        uriManagementSettings    = apiUrlManagementV1 + "/settings"
48
        uriManagementSettingsMe  = apiUrlManagementV1 + "/settings/me"
49
        uriManagementTokens      = apiUrlManagementV1 + "/settings/tokens"
50
        uriManagementToken       = apiUrlManagementV1 + "/settings/tokens/#id"
51
        uriManagementPlans       = apiUrlManagementV1 + "/plans"
52
        uriManagementPlanBinding = apiUrlManagementV1 + "/plan_binding"
53

54
        apiUrlInternalV1  = "/api/internal/v1/useradm"
55
        uriInternalAlive  = apiUrlInternalV1 + "/alive"
56
        uriInternalHealth = apiUrlInternalV1 + "/health"
57

58
        uriInternalAuthVerify  = apiUrlInternalV1 + "/auth/verify"
59
        uriInternalTenants     = apiUrlInternalV1 + "/tenants"
60
        uriInternalTenantUsers = apiUrlInternalV1 + "/tenants/#id/users"
61
        uriInternalTenantUser  = apiUrlInternalV1 + "/tenants/#id/users/#userid"
62
        uriInternalTokens      = apiUrlInternalV1 + "/tokens"
63
)
64

65
const (
66
        defaultTimeout = time.Second * 5
67
        hdrETag        = "ETag"
68
        hdrIfMatch     = "If-Match"
69
)
70

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

76
type UserAdmApiHandlers struct {
77
        userAdm useradm.App
78
        db      store.DataStore
79
        jwth    map[int]jwt.Handler
80
        config  Config
81
}
82

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

87
        JWTFallback jwt.Handler
88
}
89

90
// return an ApiHandler for user administration and authentiacation app
91
func NewUserAdmApiHandlers(
92
        userAdm useradm.App,
93
        db store.DataStore,
94
        jwth map[int]jwt.Handler,
95
        config Config,
96
) ApiHandler {
3✔
97
        return &UserAdmApiHandlers{
3✔
98
                userAdm: userAdm,
3✔
99
                db:      db,
3✔
100
                jwth:    jwth,
3✔
101
                config:  config,
3✔
102
        }
3✔
103
}
3✔
104

105
func wrapMiddleware(middleware rest.Middleware, routes ...*rest.Route) []*rest.Route {
3✔
106
        for _, route := range routes {
6✔
107
                route.Func = middleware.MiddlewareFunc(route.Func)
3✔
108
        }
3✔
109
        return routes
3✔
110
}
111

112
func (i *UserAdmApiHandlers) Build(authorizer authz.Authorizer) (http.Handler, error) {
3✔
113
        api := rest.NewApi()
3✔
114
        api.Use(
3✔
115
                // Apply common middlewares
3✔
116
                &requestlog.RequestLogMiddleware{},
3✔
117
                &accesslog.AccessLogMiddleware{
3✔
118
                        Format: accesslog.SimpleLogFormat,
3✔
119
                        DisableLog: func(statusCode int, r *rest.Request) bool {
6✔
120
                                if (r.URL.Path == uriInternalAlive ||
3✔
121
                                        r.URL.Path == uriInternalHealth) &&
3✔
122
                                        statusCode < 300 {
5✔
123
                                        return true
2✔
124
                                }
2✔
125
                                return false
3✔
126
                        },
127
                },
128
                &requestid.RequestIdMiddleware{},
129
                &rest.ContentTypeCheckerMiddleware{},
130
        )
131
        identityMiddleware := &identity.IdentityMiddleware{
3✔
132
                UpdateLogger: true,
3✔
133
        }
3✔
134
        authzMiddleware := &authz.AuthzMiddleware{
3✔
135
                Authz:              authorizer,
3✔
136
                ResFunc:            ExtractResourceAction,
3✔
137
                JWTHandlers:        i.jwth,
3✔
138
                JWTFallbackHandler: i.config.JWTFallback,
3✔
139
        }
3✔
140
        internalRoutes := []*rest.Route{
3✔
141
                rest.Get(uriInternalAlive, i.AliveHandler),
3✔
142
                rest.Get(uriInternalHealth, i.HealthHandler),
3✔
143

3✔
144
                rest.Get(uriInternalAuthVerify,
3✔
145
                        authzMiddleware.MiddlewareFunc(
3✔
146
                                identityMiddleware.MiddlewareFunc(i.AuthVerifyHandler),
3✔
147
                        ),
3✔
148
                ),
3✔
149
                rest.Post(uriInternalAuthVerify,
3✔
150
                        authzMiddleware.MiddlewareFunc(
3✔
151
                                identityMiddleware.MiddlewareFunc(i.AuthVerifyHandler),
3✔
152
                        ),
3✔
153
                ),
3✔
154
                rest.Post(uriInternalTenants, i.CreateTenantHandler),
3✔
155
                rest.Post(uriInternalTenantUsers, i.CreateTenantUserHandler),
3✔
156
                rest.Delete(uriInternalTenantUser, i.DeleteTenantUserHandler),
3✔
157
                rest.Get(uriInternalTenantUsers, i.GetTenantUsersHandler),
3✔
158
                rest.Delete(uriInternalTokens, i.DeleteTokensHandler),
3✔
159
        }
3✔
160
        mgmtRoutes := routing.AutogenOptionsRoutes([]*rest.Route{
3✔
161
                rest.Post(uriManagementAuthLogin, i.AuthLoginHandler),
3✔
162
                rest.Post(uriManagementAuthLogout, i.AuthLogoutHandler),
3✔
163
                rest.Post(uriManagementUsers, i.AddUserHandler),
3✔
164
                rest.Get(uriManagementUsers, i.GetUsersHandler),
3✔
165
                rest.Get(uriManagementUser, i.GetUserHandler),
3✔
166
                rest.Put(uriManagementUser, i.UpdateUserHandler),
3✔
167
                rest.Delete(uriManagementUser, i.DeleteUserHandler),
3✔
168
                rest.Post(uriManagementSettings, i.SaveSettingsHandler),
3✔
169
                rest.Get(uriManagementSettings, i.GetSettingsHandler),
3✔
170
                rest.Post(uriManagementSettingsMe, i.SaveSettingsMeHandler),
3✔
171
                rest.Get(uriManagementSettingsMe, i.GetSettingsMeHandler),
3✔
172
                rest.Post(uriManagementTokens, i.IssueTokenHandler),
3✔
173
                rest.Get(uriManagementTokens, i.GetTokensHandler),
3✔
174
                rest.Delete(uriManagementToken, i.DeleteTokenHandler),
3✔
175
                // plans
3✔
176
                rest.Get(uriManagementPlans, i.GetPlansHandler),
3✔
177
                rest.Get(uriManagementPlanBinding, i.GetPlanBindingHandler),
3✔
178
        }, routing.AllowHeaderOptionsGenerator)
3✔
179

3✔
180
        // IdentityMiddleware is only applied to public APIs
3✔
181
        mgmtRoutes = wrapMiddleware(identityMiddleware, mgmtRoutes...)
3✔
182

3✔
183
        routes := append(mgmtRoutes, internalRoutes...)
3✔
184
        app, err := rest.MakeRouter(routes...)
3✔
185
        if err != nil {
3✔
UNCOV
186
                return nil, errors.Wrap(err, "failed to create router")
×
UNCOV
187
        }
×
188
        api.SetApp(app)
3✔
189

3✔
190
        return api.MakeHandler(), nil
3✔
191
}
192

193
func (u *UserAdmApiHandlers) AliveHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
194
        w.WriteHeader(http.StatusNoContent)
2✔
195
}
2✔
196

197
func (u *UserAdmApiHandlers) HealthHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
198
        ctx := r.Context()
2✔
199
        l := log.FromContext(ctx)
2✔
200
        ctx, cancel := context.WithTimeout(ctx, defaultTimeout)
2✔
201
        defer cancel()
2✔
202

2✔
203
        err := u.userAdm.HealthCheck(ctx)
2✔
204
        if err != nil {
3✔
205
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusServiceUnavailable)
1✔
206
                return
1✔
207
        }
1✔
208
        w.WriteHeader(http.StatusNoContent)
2✔
209
}
210

211
func (u *UserAdmApiHandlers) AuthLoginHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
212
        ctx := r.Context()
3✔
213

3✔
214
        l := log.FromContext(ctx)
3✔
215

3✔
216
        //parse auth header
3✔
217
        user, pass, ok := r.BasicAuth()
3✔
218
        if !ok {
5✔
219
                rest_utils.RestErrWithLog(w, r, l,
2✔
220
                        ErrAuthHeader, http.StatusUnauthorized)
2✔
221
                return
2✔
222
        }
2✔
223
        email := model.Email(strings.ToLower(user))
3✔
224

3✔
225
        options := &useradm.LoginOptions{}
3✔
226
        err := r.DecodeJsonPayload(options)
3✔
227
        if err != nil && err != rest.ErrJsonPayloadEmpty {
4✔
228
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
229
                return
1✔
230
        }
1✔
231

232
        token, err := u.userAdm.Login(ctx, email, pass, options)
3✔
233
        if err != nil {
5✔
234
                switch {
2✔
235
                case err == useradm.ErrUnauthorized || err == useradm.ErrTenantAccountSuspended:
2✔
236
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnauthorized)
2✔
237
                default:
1✔
238
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
239
                }
240
                return
2✔
241
        }
242

243
        raw, err := u.userAdm.SignToken(ctx, token)
3✔
244
        if err != nil {
4✔
245
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
246
                return
1✔
247
        }
1✔
248

249
        writer := w.(http.ResponseWriter)
3✔
250
        writer.Header().Set("Content-Type", "application/jwt")
3✔
251
        _, _ = writer.Write([]byte(raw))
3✔
252
}
253

254
func (u *UserAdmApiHandlers) AuthLogoutHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
255
        ctx := r.Context()
2✔
256
        l := log.FromContext(ctx)
2✔
257

2✔
258
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
4✔
259
                keyId := jwt.GetKeyId(tokenStr)
2✔
260
                if _, ok := u.jwth[keyId]; !ok {
2✔
261
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("internal error"))
×
UNCOV
262
                        return
×
UNCOV
263
                }
×
264

265
                token, err := u.jwth[keyId].FromJWT(tokenStr)
2✔
266
                if err != nil {
3✔
267
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
268
                        return
1✔
269
                }
1✔
270
                if err := u.userAdm.Logout(ctx, token); err != nil {
3✔
271
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
272
                        return
1✔
273
                }
1✔
274
        }
275

276
        w.WriteHeader(http.StatusAccepted)
2✔
277
}
278

279
func (u *UserAdmApiHandlers) AuthVerifyHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
280
        ctx := r.Context()
3✔
281

3✔
282
        l := log.FromContext(ctx)
3✔
283

3✔
284
        // note that the request has passed through authz - the token is valid
3✔
285
        token := authz.GetRequestToken(r.Env)
3✔
286

3✔
287
        err := u.userAdm.Verify(ctx, token)
3✔
288
        if err != nil {
5✔
289
                if err == useradm.ErrUnauthorized {
4✔
290
                        rest_utils.RestErrWithLog(w, r, l, useradm.ErrUnauthorized, http.StatusUnauthorized)
2✔
291
                } else {
3✔
292
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
293
                }
1✔
294
                return
2✔
295
        }
296

297
        w.WriteHeader(http.StatusOK)
3✔
298
}
299

300
func (u *UserAdmApiHandlers) CreateTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
301
        ctx := r.Context()
2✔
302

2✔
303
        l := log.FromContext(ctx)
2✔
304

2✔
305
        user, err := parseUserInternal(r)
2✔
306
        if err != nil {
4✔
307
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
308
                return
2✔
309
        }
2✔
310

311
        tenantId := r.PathParam("id")
1✔
312
        if tenantId == "" {
2✔
313
                rest_utils.RestErrWithLog(w, r, l, errors.New("Entity not found"), http.StatusNotFound)
1✔
314
                return
1✔
315
        }
1✔
316
        ctx = getTenantContext(ctx, tenantId)
1✔
317
        err = u.userAdm.CreateUserInternal(ctx, user)
1✔
318
        if err != nil {
2✔
319
                if err == store.ErrDuplicateEmail {
2✔
320
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
1✔
321
                } else {
1✔
UNCOV
322
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
323
                }
×
324
                return
1✔
325
        }
326

327
        w.WriteHeader(http.StatusCreated)
1✔
328

329
}
330

331
func (u *UserAdmApiHandlers) AddUserHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
332
        ctx := r.Context()
2✔
333

2✔
334
        l := log.FromContext(ctx)
2✔
335

2✔
336
        user, err := parseUser(r)
2✔
337
        if err != nil {
4✔
338
                if err == model.ErrPasswordTooShort {
4✔
339
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
2✔
340
                } else {
4✔
341
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
342
                }
2✔
343
                return
2✔
344
        }
345

346
        err = u.userAdm.CreateUser(ctx, user)
2✔
347
        if err != nil {
3✔
348
                if err == store.ErrDuplicateEmail || err == useradm.ErrPassAndMailTooSimilar {
2✔
349
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
1✔
350
                } else {
1✔
UNCOV
351
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
352
                }
×
353
                return
1✔
354
        }
355

356
        w.Header().Add("Location", "users/"+string(user.ID))
2✔
357
        w.WriteHeader(http.StatusCreated)
2✔
358

359
}
360

361
func (u *UserAdmApiHandlers) GetUsersHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
362
        ctx := r.Context()
3✔
363

3✔
364
        l := log.FromContext(ctx)
3✔
365

3✔
366
        if err := r.ParseForm(); err != nil {
4✔
367
                err = errors.Wrap(err, "api: bad form parameters")
1✔
368
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
369
                return
1✔
370
        }
1✔
371

372
        fltr := model.UserFilter{}
3✔
373
        if err := fltr.ParseForm(r.Form); err != nil {
4✔
374
                err = errors.Wrap(err, "api: invalid form values")
1✔
375
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
376
                return
1✔
377
        }
1✔
378

379
        users, err := u.userAdm.GetUsers(ctx, fltr)
3✔
380
        if err != nil {
4✔
381
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
382
                return
1✔
383
        }
1✔
384

385
        _ = w.WriteJson(users)
3✔
386
}
387

388
func (u *UserAdmApiHandlers) GetTenantUsersHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
389
        ctx := r.Context()
2✔
390
        ctx = identity.WithContext(ctx, &identity.Identity{
2✔
391
                Tenant: r.PathParam("id"),
2✔
392
        })
2✔
393
        r.Request = r.Request.WithContext(ctx)
2✔
394
        u.GetUsersHandler(w, r)
2✔
395
}
2✔
396

397
func (u *UserAdmApiHandlers) GetUserHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
398
        ctx := r.Context()
2✔
399

2✔
400
        l := log.FromContext(ctx)
2✔
401

2✔
402
        id := r.PathParam("id")
2✔
403
        user, err := u.userAdm.GetUser(ctx, id)
2✔
404
        if err != nil {
3✔
405
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
406
                return
1✔
407
        }
1✔
408

409
        if user == nil {
4✔
410
                rest_utils.RestErrWithLog(w, r, l, ErrUserNotFound, 404)
2✔
411
                return
2✔
412
        }
2✔
413

414
        _ = w.WriteJson(user)
2✔
415
}
416

417
func (u *UserAdmApiHandlers) UpdateUserHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
418
        ctx := r.Context()
2✔
419
        idty := identity.FromContext(ctx)
2✔
420
        l := log.FromContext(ctx)
2✔
421

2✔
422
        userUpdate, err := parseUserUpdate(r)
2✔
423
        if err != nil {
4✔
424
                if err == model.ErrPasswordTooShort {
3✔
425
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
1✔
426
                } else {
3✔
427
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
428
                }
2✔
429
                return
2✔
430
        }
431

432
        // extract the token used to update the user
433
        if tokenStr, err := authz.ExtractToken(r.Request); err == nil {
4✔
434
                keyId := jwt.GetKeyId(tokenStr)
2✔
435
                if _, ok := u.jwth[keyId]; !ok {
2✔
UNCOV
436
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("internal error"))
×
UNCOV
437
                        return
×
UNCOV
438
                }
×
439

440
                token, err := u.jwth[keyId].FromJWT(tokenStr)
2✔
441
                if err != nil {
2✔
UNCOV
442
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
443
                        return
×
UNCOV
444
                }
×
445
                userUpdate.Token = token
2✔
446
        }
447

448
        id := r.PathParam("id")
2✔
449
        if strings.EqualFold(id, "me") {
4✔
450
                id = idty.Subject
2✔
451
        }
2✔
452
        err = u.userAdm.UpdateUser(ctx, id, userUpdate)
2✔
453
        if err != nil {
4✔
454
                switch err {
2✔
455
                case store.ErrDuplicateEmail,
456
                        useradm.ErrCurrentPasswordMismatch,
457
                        useradm.ErrPassAndMailTooSimilar,
458
                        useradm.ErrCannotModifyPassword:
2✔
459
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusUnprocessableEntity)
2✔
460
                case store.ErrUserNotFound:
1✔
461
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusNotFound)
1✔
462
                case useradm.ErrETagMismatch:
1✔
463
                        rest_utils.RestErrWithLog(w, r, l, err, http.StatusConflict)
1✔
UNCOV
464
                default:
×
UNCOV
465
                        rest_utils.RestErrWithLogInternal(w, r, l, err)
×
466
                }
467
        }
468

469
        w.WriteHeader(http.StatusNoContent)
2✔
470
}
471

472
func (u *UserAdmApiHandlers) DeleteTenantUserHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
473
        ctx := r.Context()
2✔
474

2✔
475
        tenantId := r.PathParam("id")
2✔
476
        if tenantId != "" {
4✔
477
                ctx = getTenantContext(ctx, tenantId)
2✔
478
        }
2✔
479

480
        l := log.FromContext(ctx)
2✔
481
        err := u.userAdm.DeleteUser(ctx, r.PathParam("userid"))
2✔
482
        if err != nil {
3✔
483
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
484
                return
1✔
485
        }
1✔
486

487
        w.WriteHeader(http.StatusNoContent)
2✔
488
}
489

490
func (u *UserAdmApiHandlers) DeleteUserHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
491
        ctx := r.Context()
2✔
492

2✔
493
        l := log.FromContext(ctx)
2✔
494

2✔
495
        err := u.userAdm.DeleteUser(ctx, r.PathParam("id"))
2✔
496
        if err != nil {
3✔
497
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
498
                return
1✔
499
        }
1✔
500

501
        w.WriteHeader(http.StatusNoContent)
2✔
502
}
503

504
func parseUser(r *rest.Request) (*model.User, error) {
2✔
505
        user := model.User{}
2✔
506

2✔
507
        //decode body
2✔
508
        err := r.DecodeJsonPayload(&user)
2✔
509
        if err != nil {
3✔
510
                return nil, errors.Wrap(err, "failed to decode request body")
1✔
511
        }
1✔
512

513
        if err := user.Validate(); err != nil {
4✔
514
                return nil, err
2✔
515
        }
2✔
516

517
        return &user, nil
2✔
518
}
519

520
func parseUserInternal(r *rest.Request) (*model.UserInternal, error) {
2✔
521
        user := model.UserInternal{}
2✔
522

2✔
523
        //decode body
2✔
524
        err := r.DecodeJsonPayload(&user)
2✔
525
        if err != nil {
4✔
526
                return nil, errors.Wrap(err, "failed to decode request body")
2✔
527
        }
2✔
528

529
        if err := user.Validate(); err != nil {
2✔
530
                return nil, err
1✔
531
        }
1✔
532

533
        return &user, nil
1✔
534
}
535

536
func parseUserUpdate(r *rest.Request) (*model.UserUpdate, error) {
2✔
537
        userUpdate := model.UserUpdate{}
2✔
538

2✔
539
        //decode body
2✔
540
        err := r.DecodeJsonPayload(&userUpdate)
2✔
541
        if err != nil {
3✔
542
                return nil, errors.Wrap(err, "failed to decode request body")
1✔
543
        }
1✔
544

545
        if err := userUpdate.Validate(); err != nil {
4✔
546
                return nil, err
2✔
547
        }
2✔
548

549
        return &userUpdate, nil
2✔
550
}
551

552
func (u *UserAdmApiHandlers) CreateTenantHandler(w rest.ResponseWriter, r *rest.Request) {
3✔
553
        ctx := r.Context()
3✔
554

3✔
555
        l := log.FromContext(ctx)
3✔
556

3✔
557
        var newTenant model.NewTenant
3✔
558

3✔
559
        if err := r.DecodeJsonPayload(&newTenant); err != nil {
5✔
560
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
561
                return
2✔
562
        }
2✔
563

564
        if err := newTenant.Validate(); err != nil {
4✔
565
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
2✔
566
                return
2✔
567
        }
2✔
568

569
        err := u.userAdm.CreateTenant(ctx, model.NewTenant{
2✔
570
                ID: newTenant.ID,
2✔
571
        })
2✔
572
        if err != nil {
3✔
573
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
574
                return
1✔
575
        }
1✔
576

577
        w.WriteHeader(http.StatusCreated)
2✔
578
}
579

580
func getTenantContext(ctx context.Context, tenantId string) context.Context {
2✔
581
        if ctx == nil {
2✔
UNCOV
582
                ctx = context.Background()
×
UNCOV
583
        }
×
584
        if tenantId != "" {
4✔
585
                id := &identity.Identity{
2✔
586
                        Tenant: tenantId,
2✔
587
                }
2✔
588

2✔
589
                ctx = identity.WithContext(ctx, id)
2✔
590
        }
2✔
591

592
        return ctx
2✔
593
}
594

595
func (u *UserAdmApiHandlers) DeleteTokensHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
596

2✔
597
        ctx := r.Context()
2✔
598

2✔
599
        l := log.FromContext(ctx)
2✔
600

2✔
601
        tenantId := r.URL.Query().Get("tenant_id")
2✔
602
        if tenantId == "" {
4✔
603
                rest_utils.RestErrWithLog(
2✔
604
                        w,
2✔
605
                        r,
2✔
606
                        l,
2✔
607
                        errors.New("tenant_id must be provided"),
2✔
608
                        http.StatusBadRequest,
2✔
609
                )
2✔
610
                return
2✔
611
        }
2✔
612
        userId := r.URL.Query().Get("user_id")
1✔
613

1✔
614
        err := u.userAdm.DeleteTokens(ctx, tenantId, userId)
1✔
615
        switch err {
1✔
616
        case nil:
1✔
617
                w.WriteHeader(http.StatusNoContent)
1✔
618
        default:
1✔
619
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
620
        }
621
}
622

623
func (u *UserAdmApiHandlers) SaveSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
624
        u.saveSettingsHandler(w, r, false)
2✔
625
}
2✔
626

UNCOV
627
func (u *UserAdmApiHandlers) SaveSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
628
        u.saveSettingsHandler(w, r, true)
×
UNCOV
629
}
×
630

631
func (u *UserAdmApiHandlers) saveSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
2✔
632
        ctx := r.Context()
2✔
633

2✔
634
        l := log.FromContext(ctx)
2✔
635

2✔
636
        settings := &model.Settings{}
2✔
637
        err := r.DecodeJsonPayload(settings)
2✔
638
        if err != nil {
4✔
639
                rest_utils.RestErrWithLog(
2✔
640
                        w,
2✔
641
                        r,
2✔
642
                        l,
2✔
643
                        errors.New("cannot parse request body as json"),
2✔
644
                        http.StatusBadRequest,
2✔
645
                )
2✔
646
                return
2✔
647
        }
2✔
648

649
        if err := settings.Validate(); err != nil {
3✔
650
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
651
                return
1✔
652
        }
1✔
653

654
        ifMatchHeader := r.Header.Get(hdrIfMatch)
2✔
655

2✔
656
        settings.ETag = uuid.NewString()
2✔
657
        if me {
2✔
UNCOV
658
                id := identity.FromContext(ctx)
×
UNCOV
659
                if id == nil {
×
660
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
661
                        return
×
662
                }
×
UNCOV
663
                err = u.db.SaveUserSettings(ctx, id.Subject, settings, ifMatchHeader)
×
664
        } else {
2✔
665
                err = u.db.SaveSettings(ctx, settings, ifMatchHeader)
2✔
666
        }
2✔
667
        if err == store.ErrETagMismatch {
3✔
668
                rest_utils.RestErrWithInfoMsg(w, r, l, err, http.StatusPreconditionFailed, err.Error())
1✔
669
                return
1✔
670
        } else if err != nil {
4✔
671
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
672
                return
1✔
673
        }
1✔
674

675
        w.WriteHeader(http.StatusCreated)
2✔
676
}
677

678
func (u *UserAdmApiHandlers) GetSettingsHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
679
        u.getSettingsHandler(w, r, false)
2✔
680
}
2✔
681

UNCOV
682
func (u *UserAdmApiHandlers) GetSettingsMeHandler(w rest.ResponseWriter, r *rest.Request) {
×
UNCOV
683
        u.getSettingsHandler(w, r, true)
×
UNCOV
684
}
×
685

686
func (u *UserAdmApiHandlers) getSettingsHandler(w rest.ResponseWriter, r *rest.Request, me bool) {
2✔
687
        ctx := r.Context()
2✔
688

2✔
689
        l := log.FromContext(ctx)
2✔
690

2✔
691
        var settings *model.Settings
2✔
692
        var err error
2✔
693
        if me {
2✔
UNCOV
694
                id := identity.FromContext(ctx)
×
UNCOV
695
                if id == nil {
×
UNCOV
696
                        rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
UNCOV
697
                        return
×
UNCOV
698
                }
×
UNCOV
699
                settings, err = u.db.GetUserSettings(ctx, id.Subject)
×
700
        } else {
2✔
701
                settings, err = u.db.GetSettings(ctx)
2✔
702
        }
2✔
703

704
        if err != nil {
3✔
705
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
706
                return
1✔
707
        } else if settings == nil {
4✔
708
                settings = &model.Settings{
1✔
709
                        Values: model.SettingsValues{},
1✔
710
                }
1✔
711
        }
1✔
712

713
        if settings.ETag != "" {
4✔
714
                w.Header().Set(hdrETag, settings.ETag)
2✔
715
        }
2✔
716
        _ = w.WriteJson(settings)
2✔
717
}
718

719
func (u *UserAdmApiHandlers) IssueTokenHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
720
        ctx := r.Context()
2✔
721

2✔
722
        l := log.FromContext(ctx)
2✔
723

2✔
724
        var tokenRequest model.TokenRequest
2✔
725

2✔
726
        if err := r.DecodeJsonPayload(&tokenRequest); err != nil {
2✔
UNCOV
727
                rest_utils.RestErrWithLog(
×
UNCOV
728
                        w,
×
UNCOV
729
                        r,
×
UNCOV
730
                        l,
×
UNCOV
731
                        errors.New("cannot parse request body as json"),
×
UNCOV
732
                        http.StatusBadRequest,
×
UNCOV
733
                )
×
UNCOV
734
                return
×
UNCOV
735
        }
×
736
        if err := tokenRequest.Validate(u.config.TokenMaxExpSeconds); err != nil {
3✔
737
                rest_utils.RestErrWithLog(
1✔
738
                        w,
1✔
739
                        r,
1✔
740
                        l,
1✔
741
                        err,
1✔
742
                        http.StatusBadRequest,
1✔
743
                )
1✔
744
                return
1✔
745
        }
1✔
746

747
        token, err := u.userAdm.IssuePersonalAccessToken(ctx, &tokenRequest)
2✔
748
        switch err {
2✔
749
        case nil:
2✔
750
                writer := w.(http.ResponseWriter)
2✔
751
                writer.Header().Set("Content-Type", "application/jwt")
2✔
752
                _, _ = writer.Write([]byte(token))
2✔
753
        case useradm.ErrTooManyTokens:
2✔
754
                rest_utils.RestErrWithLog(
2✔
755
                        w,
2✔
756
                        r,
2✔
757
                        l,
2✔
758
                        err,
2✔
759
                        http.StatusUnprocessableEntity,
2✔
760
                )
2✔
761
        case useradm.ErrDuplicateTokenName:
2✔
762
                rest_utils.RestErrWithLog(
2✔
763
                        w,
2✔
764
                        r,
2✔
765
                        l,
2✔
766
                        err,
2✔
767
                        http.StatusConflict,
2✔
768
                )
2✔
UNCOV
769
        default:
×
UNCOV
770
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
771
        }
772
}
773

774
func (u *UserAdmApiHandlers) GetTokensHandler(w rest.ResponseWriter, r *rest.Request) {
2✔
775
        ctx := r.Context()
2✔
776
        l := log.FromContext(ctx)
2✔
777
        id := identity.FromContext(ctx)
2✔
778
        if id == nil {
2✔
UNCOV
779
                rest_utils.RestErrWithLogInternal(w, r, l, errors.New("identity not present"))
×
UNCOV
780
                return
×
UNCOV
781
        }
×
782

783
        tokens, err := u.userAdm.GetPersonalAccessTokens(ctx, id.Subject)
2✔
784
        if err != nil {
3✔
785
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
786
                return
1✔
787
        }
1✔
788

789
        _ = w.WriteJson(tokens)
2✔
790
}
791

792
func (u *UserAdmApiHandlers) DeleteTokenHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
793
        ctx := r.Context()
1✔
794

1✔
795
        l := log.FromContext(ctx)
1✔
796

1✔
797
        err := u.userAdm.DeleteToken(ctx, r.PathParam("id"))
1✔
798
        if err != nil {
1✔
UNCOV
799
                rest_utils.RestErrWithLogInternal(w, r, l, err)
×
UNCOV
800
                return
×
UNCOV
801
        }
×
802

803
        w.WriteHeader(http.StatusNoContent)
1✔
804
}
805

806
// plans and plan binding
807

808
func (u *UserAdmApiHandlers) GetPlansHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
809
        ctx := r.Context()
1✔
810
        l := log.FromContext(ctx)
1✔
811
        page, perPage, err := rest_utils.ParsePagination(r)
1✔
812
        if err != nil {
2✔
813
                rest_utils.RestErrWithLog(w, r, l, err, http.StatusBadRequest)
1✔
814
                return
1✔
815
        }
1✔
816

817
        plans := u.userAdm.GetPlans(ctx, int((page-1)*perPage), int(perPage))
1✔
818
        if plans == nil {
2✔
819
                plans = []model.Plan{}
1✔
820
        }
1✔
821

822
        _ = w.WriteJson(plans)
1✔
823
}
824

825
func (u *UserAdmApiHandlers) GetPlanBindingHandler(w rest.ResponseWriter, r *rest.Request) {
1✔
826
        ctx := r.Context()
1✔
827
        l := log.FromContext(ctx)
1✔
828

1✔
829
        planBinding, err := u.userAdm.GetPlanBinding(ctx)
1✔
830
        if err != nil {
2✔
831
                rest_utils.RestErrWithLogInternal(w, r, l, err)
1✔
832
                return
1✔
833
        }
1✔
834

835
        _ = w.WriteJson(planBinding)
1✔
836
}
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