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

mendersoftware / mender-server / 1902461369

02 Jul 2025 01:23PM UTC coverage: 65.478% (-0.1%) from 65.622%
1902461369

push

gitlab-ci

web-flow
Merge pull request #764 from mendersoftware/MEN-8237

refactor(useradm): Migrate from ant0nie/go-json-rest to gin-gonic/gin

265 of 312 new or added lines in 6 files covered. (84.94%)

36 existing lines in 6 files now uncovered.

32157 of 49111 relevant lines covered (65.48%)

1.39 hits per line

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

97.84
/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 {
9✔
38
        return new(MiddlewareOptions)
9✔
39
}
9✔
40

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

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

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

9✔
88
        c.Request = c.Request.WithContext(ctx)
9✔
89
        return
9✔
90
exitUnauthorized:
9✔
91
        c.Header("WWW-Authenticate", `Bearer realm="ManagementJWT"`)
5✔
92
        urest.RenderError(c, http.StatusUnauthorized, err)
5✔
93
        c.Abort()
5✔
94
}
95

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

120
func Middleware(opts ...*MiddlewareOptions) gin.HandlerFunc {
9✔
121

9✔
122
        var middleware gin.HandlerFunc
9✔
123

9✔
124
        // Initialize default options
9✔
125
        opt := NewMiddlewareOptions().
9✔
126
                SetUpdateLogger(true)
9✔
127
        for _, o := range opts {
12✔
128
                if o == nil {
4✔
129
                        continue
1✔
130
                }
131
                if o.PathRegex != nil {
6✔
132
                        opt.PathRegex = o.PathRegex
3✔
133
                }
3✔
134
                if o.UpdateLogger != nil {
4✔
135
                        opt.UpdateLogger = o.UpdateLogger
1✔
136
                }
1✔
137
        }
138

139
        if *opt.UpdateLogger {
18✔
140
                middleware = middlewareWithLogger
9✔
141
        } else {
10✔
142
                middleware = middlewareBase
1✔
143
        }
1✔
144

145
        if opt.PathRegex != nil {
12✔
146
                pathRegex := regexp.MustCompile(*opt.PathRegex)
3✔
147
                return func(c *gin.Context) {
6✔
148
                        if !pathRegex.MatchString(c.FullPath()) {
6✔
149
                                return
3✔
150
                        }
3✔
151
                        middleware(c)
3✔
152
                }
153
        }
154
        return middleware
8✔
155
}
156

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

172
// MiddlewareFunc makes IdentityMiddleware implement the Middleware interface.
173
func (mw *IdentityMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc {
1✔
174
        return func(w rest.ResponseWriter, r *rest.Request) {
2✔
175
                jwt, err := ExtractJWTFromHeader(r.Request)
1✔
176
                if err != nil {
2✔
177
                        h(w, r)
1✔
178
                        return
1✔
179
                }
1✔
180

181
                ctx := r.Context()
1✔
182
                l := log.FromContext(ctx)
1✔
183

1✔
184
                identity, err := ExtractIdentity(jwt)
1✔
185
                if err != nil {
2✔
186
                        l.Warnf("Failed to parse extracted JWT: %s",
1✔
187
                                err.Error(),
1✔
188
                        )
1✔
189
                } else {
2✔
190
                        if mw.UpdateLogger {
2✔
191
                                logCtx := log.Ctx{}
1✔
192

1✔
193
                                key := "sub"
1✔
194
                                if identity.IsDevice {
2✔
195
                                        key = "device_id"
1✔
196
                                } else if identity.IsUser {
3✔
197
                                        key = "user_id"
1✔
198
                                }
1✔
199

200
                                logCtx[key] = identity.Subject
1✔
201

1✔
202
                                if identity.Tenant != "" {
2✔
203
                                        logCtx["tenant_id"] = identity.Tenant
1✔
204
                                }
1✔
205

206
                                if identity.Plan != "" {
2✔
207
                                        logCtx["plan"] = identity.Plan
1✔
208
                                }
1✔
209

210
                                l = l.F(logCtx)
1✔
211
                                ctx = log.WithContext(ctx, l)
1✔
212
                        }
213
                        ctx = WithContext(ctx, &identity)
1✔
214
                        r.Request = r.WithContext(ctx)
1✔
215
                }
216

217
                h(w, r)
1✔
218
        }
219
}
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