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

mendersoftware / mender-server / 1863989307

11 Jun 2025 08:38AM UTC coverage: 66.403% (+0.7%) from 65.731%
1863989307

Pull #720

gitlab-ci

mzedel
test(gui): made e2e tests work with changed tenant token expansion

Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #720: MEN-8411 - billing/ organization split

29581 of 44548 relevant lines covered (66.4%)

1.45 hits per line

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

80.74
/backend/pkg/identity/middleware.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 identity
16

17
import (
18
        "net/http"
19
        "regexp"
20

21
        "github.com/ant0ine/go-json-rest/rest"
22
        "github.com/gin-gonic/gin"
23

24
        "github.com/mendersoftware/mender-server/pkg/log"
25
        urest "github.com/mendersoftware/mender-server/pkg/rest.utils"
26
)
27

28
type MiddlewareOptions struct {
29
        // PathRegex sets the regex for the path for which this middleware
30
        // applies. Defaults to "^/api/management/v[0-9.]{1,6}/.+".
31
        PathRegex *string
32

33
        // UpdateLogger adds the decoded identity to the log context.
34
        UpdateLogger *bool
35
}
36

37
func NewMiddlewareOptions() *MiddlewareOptions {
6✔
38
        return new(MiddlewareOptions)
6✔
39
}
6✔
40

41
func (opts *MiddlewareOptions) SetPathRegex(regex string) *MiddlewareOptions {
2✔
42
        opts.PathRegex = &regex
2✔
43
        return opts
2✔
44
}
2✔
45

46
func (opts *MiddlewareOptions) SetUpdateLogger(updateLogger bool) *MiddlewareOptions {
6✔
47
        opts.UpdateLogger = &updateLogger
6✔
48
        return opts
6✔
49
}
6✔
50

51
func middlewareWithLogger(c *gin.Context) {
6✔
52
        var (
6✔
53
                err    error
6✔
54
                jwt    string
6✔
55
                idty   Identity
6✔
56
                logCtx = log.Ctx{}
6✔
57
                key    = "sub"
6✔
58
                ctx    = c.Request.Context()
6✔
59
                l      = log.FromContext(ctx)
6✔
60
        )
6✔
61
        jwt, err = ExtractJWTFromHeader(c.Request)
6✔
62
        if err != nil {
8✔
63
                goto exitUnauthorized
2✔
64
        }
65
        idty, err = ExtractIdentity(jwt)
6✔
66
        if err != nil {
6✔
67
                goto exitUnauthorized
×
68
        }
69
        ctx = WithContext(ctx, &idty)
6✔
70
        if idty.IsDevice {
10✔
71
                key = "device_id"
4✔
72
        } else if idty.IsUser {
15✔
73
                key = "user_id"
5✔
74
        }
5✔
75
        logCtx[key] = idty.Subject
6✔
76
        if idty.Tenant != "" {
9✔
77
                logCtx["tenant_id"] = idty.Tenant
3✔
78
        }
3✔
79
        if idty.Plan != "" {
9✔
80
                logCtx["plan"] = idty.Plan
3✔
81
        }
3✔
82
        ctx = log.WithContext(ctx, l.F(logCtx))
6✔
83

6✔
84
        c.Request = c.Request.WithContext(ctx)
6✔
85
        return
6✔
86
exitUnauthorized:
6✔
87
        c.Header("WWW-Authenticate", `Bearer realm="ManagementJWT"`)
2✔
88
        urest.RenderError(c, http.StatusUnauthorized, err)
2✔
89
        c.Abort()
2✔
90
}
91

92
func middlewareBase(c *gin.Context) {
×
93
        var (
×
94
                err  error
×
95
                jwt  string
×
96
                idty Identity
×
97
                ctx  = c.Request.Context()
×
98
        )
×
99
        jwt, err = ExtractJWTFromHeader(c.Request)
×
100
        if err != nil {
×
101
                goto exitUnauthorized
×
102
        }
103
        idty, err = ExtractIdentity(jwt)
×
104
        if err != nil {
×
105
                goto exitUnauthorized
×
106
        }
107
        ctx = WithContext(ctx, &idty)
×
108
        c.Request = c.Request.WithContext(ctx)
×
109
        return
×
110
exitUnauthorized:
×
111
        c.Header("WWW-Authenticate", `Bearer realm="ManagementJWT"`)
×
112
        urest.RenderError(c, http.StatusUnauthorized, err)
×
113
        c.Abort()
×
114
}
115

116
func Middleware(opts ...*MiddlewareOptions) gin.HandlerFunc {
6✔
117

6✔
118
        var middleware gin.HandlerFunc
6✔
119

6✔
120
        // Initialize default options
6✔
121
        opt := NewMiddlewareOptions().
6✔
122
                SetUpdateLogger(true)
6✔
123
        for _, o := range opts {
8✔
124
                if o == nil {
2✔
125
                        continue
×
126
                }
127
                if o.PathRegex != nil {
4✔
128
                        opt.PathRegex = o.PathRegex
2✔
129
                }
2✔
130
                if o.UpdateLogger != nil {
2✔
131
                        opt.UpdateLogger = o.UpdateLogger
×
132
                }
×
133
        }
134

135
        if *opt.UpdateLogger {
12✔
136
                middleware = middlewareWithLogger
6✔
137
        } else {
6✔
138
                middleware = middlewareBase
×
139
        }
×
140

141
        if opt.PathRegex != nil {
8✔
142
                pathRegex := regexp.MustCompile(*opt.PathRegex)
2✔
143
                return func(c *gin.Context) {
4✔
144
                        if !pathRegex.MatchString(c.FullPath()) {
4✔
145
                                return
2✔
146
                        }
2✔
147
                        middleware(c)
2✔
148
                }
149
        }
150
        return middleware
5✔
151
}
152

153
// IdentityMiddleware adds the identity extracted from JWT token to the request's context.
154
// IdentityMiddleware does not perform any form of token signature verification.
155
// If it is not possible to extract identity from header error log will be generated.
156
// IdentityMiddleware will not stop control propagating through the chain in any case.
157
// It is recommended to use IdentityMiddleware with RequestLogMiddleware and
158
// RequestLogMiddleware should be placed before IdentityMiddleware.
159
// Otherwise, log generated by IdentityMiddleware will not contain "request_id" field.
160
type IdentityMiddleware struct {
161
        // If set to true, the middleware will update context logger setting
162
        // 'user_id' or 'device_id' to the value of subject field, if the token
163
        // is not a user or a device token, the middelware will add a 'sub'
164
        // field to the logger
165
        UpdateLogger bool
166
}
167

168
// MiddlewareFunc makes IdentityMiddleware implement the Middleware interface.
169
func (mw *IdentityMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc {
3✔
170
        return func(w rest.ResponseWriter, r *rest.Request) {
6✔
171
                jwt, err := ExtractJWTFromHeader(r.Request)
3✔
172
                if err != nil {
6✔
173
                        h(w, r)
3✔
174
                        return
3✔
175
                }
3✔
176

177
                ctx := r.Context()
3✔
178
                l := log.FromContext(ctx)
3✔
179

3✔
180
                identity, err := ExtractIdentity(jwt)
3✔
181
                if err != nil {
5✔
182
                        l.Warnf("Failed to parse extracted JWT: %s",
2✔
183
                                err.Error(),
2✔
184
                        )
2✔
185
                } else {
5✔
186
                        if mw.UpdateLogger {
6✔
187
                                logCtx := log.Ctx{}
3✔
188

3✔
189
                                key := "sub"
3✔
190
                                if identity.IsDevice {
5✔
191
                                        key = "device_id"
2✔
192
                                } else if identity.IsUser {
8✔
193
                                        key = "user_id"
3✔
194
                                }
3✔
195

196
                                logCtx[key] = identity.Subject
3✔
197

3✔
198
                                if identity.Tenant != "" {
4✔
199
                                        logCtx["tenant_id"] = identity.Tenant
1✔
200
                                }
1✔
201

202
                                if identity.Plan != "" {
4✔
203
                                        logCtx["plan"] = identity.Plan
1✔
204
                                }
1✔
205

206
                                l = l.F(logCtx)
3✔
207
                                ctx = log.WithContext(ctx, l)
3✔
208
                        }
209
                        ctx = WithContext(ctx, &identity)
3✔
210
                        r.Request = r.WithContext(ctx)
3✔
211
                }
212

213
                h(w, r)
3✔
214
        }
215
}
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